Implement web socket handshake

This commit is contained in:
Arijit Das 2020-12-01 18:13:59 +05:30
commit 3b69187007
6 changed files with 145 additions and 0 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*]
indent_style = space
insert_final_newline = true
indent_size = 2
trim_trailing_whitespace = true

21
Readme.md Normal file
View File

@ -0,0 +1,21 @@
Websocket for Nim
We're working towards an implementation of the
[Websocket](https://tools.ietf.org/html/rfc6455) protocol for
[Nim](https://nim-lang.org/). This is very much a work in progress, and not yet
in a usable state.
Building and testing
--------------------
Install dependencies:
```bash
nimble install -d
```
Run tests:
```bash
nimble test
```

14
lint.nims Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env nim
import std/strutils
proc lintFile*(file: string) =
if file.endsWith(".nim"):
exec "nimpretty " & file
proc lintDir*(dir: string) =
for file in listFiles(dir):
lintFile(file)
for subdir in listDirs(dir):
lintDir(subdir)
lintDir(projectDir())

8
test/server.nim Normal file
View File

@ -0,0 +1,8 @@
import ../ws, chronos, asynchttpserver
proc cb(req: Request) {.async.} =
var ws = await newWebSocket(req)
ws.close()
var server = newAsyncHttpServer()
waitFor server.serve(Port(9001), cb)

11
ws.nimble Normal file
View File

@ -0,0 +1,11 @@
packageName = "ws"
version = "0.1.0"
author = "Status Research & Development GmbH"
description = "WS protocol implementation"
license = "MIT"
requires "nim >= 1.2.6"
requires "chronos >= 2.5.2 & < 3.0.0"
task lint, "format source files according to the official style guide":
exec "./lint.nims"

86
ws/ws.nim Normal file
View File

@ -0,0 +1,86 @@
import chronos, asyncdispatch, asynchttpserver, base64, nativesockets
type HeaderVerificationError* {.pure.} = enum
None
## No error.
UnsupportedVersion
## The Sec-Websocket-Version header gave an unsupported version.
## The only currently supported version is 13.
NoKey
## No Sec-Websocket-Key was provided.
ProtocolAdvertised
## A protocol was advertised but the server gave no protocol.
NoProtocolsSupported
## None of the advertised protocols match the server protocol.
NoProtocolAdvertised
## Server asked for a protocol but no protocol was advertised.
proc `$`*(error: HeaderVerificationError): string =
const errorTable: array[HeaderVerificationError, string] = [
"no error",
"the only supported sec-websocket-version is 13",
"no sec-websocket-key provided",
"server does not support protocol negotation",
"no advertised protocol supported",
"no protocol advertised"
]
result = errorTable[error]
type
ReadyState* = enum
Connecting = 0 # The connection is not yet open.
Open = 1 # The connection is open and ready to communicate.
Closing = 2 # The connection is in the process of closing.
Closed = 3 # The connection is closed or couldn't be opened.
WebSocket* = ref object
tcpSocket*: AsyncSocket
version*: int
key*: string
protocol*: string
readyState*: ReadyState
masked*: bool # send masked packets
WebSocketError* = object of IOError
proc handshake*(ws: WebSocket, headers: HttpHeaders): Future[error: HeaderVerificationError] {.async.} =
ws.version = parseInt(headers["Sec-WebSocket-Version"])
ws.key = headers["Sec-WebSocket-Key"].strip()
if headers.hasKey("Sec-WebSocket-Protocol"):
let wantProtocol = headers["Sec-WebSocket-Protocol"].strip()
if ws.protocol != wantProtocol:
return NoProtocolsSupported
let
sh = secureHash(ws.key & "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
acceptKey = base64.encode(decodeBase16($sh))
var response = "HTTP/1.1 101 Web Socket Protocol Handshake\c\L"
response.add("Sec-WebSocket-Accept: " & acceptKey & "\c\L")
response.add("Connection: Upgrade\c\L")
response.add("Upgrade: webSocket\c\L")
if ws.protocol != "":
response.add("Sec-WebSocket-Protocol: " & ws.protocol & "\c\L")
response.add "\c\L"
await ws.tcpSocket.send(response)
ws.readyState = Open
proc newWebSocket*(req: Request, protocol: string = ""): Future[tuple[ws: AsyncWebSocket, error: HeaderVerificationError]] {.async.} =
if not req.headers.hasKey("Sec-WebSocket-Version"):
return ("", UnsupportedVersion)
var ws = WebSocket()
ws.masked = false
ws.tcpSocket = req.client
ws.protocol = protocol
let (ws, error) = await ws.handshake(req.headers)
return ws, error
proc close*(ws: WebSocket) =
ws.readyState = Closed
proc close() {.async.} =
await ws.send("", Close)
ws.tcpSocket.close()
asyncCheck close()