nim-websock/ws/ws.nim

87 lines
2.9 KiB
Nim

import chronos, 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()