mirror of
https://github.com/codex-storage/nim-websock.git
synced 2025-02-02 13:53:21 +00:00
Remove ws.nim file.
This commit is contained in:
parent
350fa94418
commit
8e34e0f138
571
src/ws.nim
571
src/ws.nim
@ -1,571 +0,0 @@
|
||||
import chronos, chronicles, httputils, strutils, base64, std/sha1, random,
|
||||
streams, nativesockets, uri, times, chronos.timer, tables
|
||||
|
||||
const
|
||||
MaxHttpHeadersSize = 8192 # maximum size of HTTP headers in octets
|
||||
MaxHttpRequestSize = 128 * 1024 # maximum size of HTTP body in octets
|
||||
HttpHeadersTimeout = timer.seconds(120) # timeout for receiving headers (120 sec)
|
||||
HttpBodyTimeout = timer.seconds(12) # timeout for receiving body (12 sec)
|
||||
HeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)]
|
||||
WebsocketUserAgent* = "nim-ws (https://github.com/status-im/nim-ws)"
|
||||
|
||||
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*: StreamTransport
|
||||
version*: int
|
||||
key*: string
|
||||
protocol*: string
|
||||
readyState*: ReadyState
|
||||
masked*: bool # send masked packets
|
||||
header*: HttpHeaders
|
||||
|
||||
AsyncCallback = proc (transp: StreamTransport,
|
||||
header: HttpRequestHeader): Future[void] {.closure, gcsafe.}
|
||||
HttpServer* = ref object of StreamServer
|
||||
callback: AsyncCallback
|
||||
|
||||
ReqStatus = enum
|
||||
Success, Error, ErrorFailure
|
||||
|
||||
WebSocketError* = object of IOError
|
||||
|
||||
HttpHeaders* = ref object
|
||||
table*: TableRef[string, seq[string]]
|
||||
|
||||
type
|
||||
AsyncHttpClient* = ref object
|
||||
connected: bool
|
||||
currentURL: Url ## Where we are currently connected.
|
||||
headers: seq[HttpHeader] ## Headers to send in requests.
|
||||
userAgent: string
|
||||
contentTotal: BiggestInt
|
||||
contentProgress: BiggestInt
|
||||
oneSecondProgress: BiggestInt
|
||||
transp: StreamTransport
|
||||
buf: seq[byte]
|
||||
|
||||
template `[]`(value: uint8, index: int): bool =
|
||||
## Get bits from uint8, uint8[2] gets 2nd bit.
|
||||
(value and (1 shl (7 - index))) != 0
|
||||
|
||||
proc nibbleFromChar(c: char): int =
|
||||
## Converts hex chars like `0` to 0 and `F` to 15.
|
||||
case c:
|
||||
of '0'..'9': (ord(c) - ord('0'))
|
||||
of 'a'..'f': (ord(c) - ord('a') + 10)
|
||||
of 'A'..'F': (ord(c) - ord('A') + 10)
|
||||
else: 255
|
||||
|
||||
proc nibbleToChar(value: int): char =
|
||||
## Converts number like 0 to `0` and 15 to `fg`.
|
||||
case value:
|
||||
of 0..9: char(value + ord('0'))
|
||||
else: char(value + ord('a') - 10)
|
||||
|
||||
proc decodeBase16*(str: string): string =
|
||||
## Base16 decode a string.
|
||||
result = newString(str.len div 2)
|
||||
for i in 0 ..< result.len:
|
||||
result[i] = chr(
|
||||
(nibbleFromChar(str[2 * i]) shl 4) or
|
||||
nibbleFromChar(str[2 * i + 1]))
|
||||
|
||||
proc encodeBase16*(str: string): string =
|
||||
## Base61 encode a string.
|
||||
result = newString(str.len * 2)
|
||||
for i, c in str:
|
||||
result[i * 2] = nibbleToChar(ord(c) shr 4)
|
||||
result[i * 2 + 1] = nibbleToChar(ord(c) and 0x0f)
|
||||
|
||||
proc genMaskKey(): array[4, char] =
|
||||
## Generates a random key of 4 random chars.
|
||||
proc r(): char = char(rand(255))
|
||||
[r(), r(), r(), r()]
|
||||
|
||||
proc handshake*(ws: WebSocket, header: HttpRequestHeader) {.async.} =
|
||||
## Handles the websocket handshake.
|
||||
ws.version = parseInt(header["Sec-WebSocket-Version"])
|
||||
ws.key = header["Sec-WebSocket-Key"].strip()
|
||||
if header.contains("Sec-WebSocket-Protocol"):
|
||||
let wantProtocol = header["Sec-WebSocket-Protocol"].strip()
|
||||
if ws.protocol != wantProtocol:
|
||||
raise newException(WebSocketError,
|
||||
"Protocol mismatch (expected: " & ws.protocol & ", got: " &
|
||||
wantProtocol & ")")
|
||||
|
||||
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"
|
||||
|
||||
discard await ws.tcpSocket.write(response)
|
||||
ws.readyState = Open
|
||||
|
||||
proc newWebSocket*(header: HttpRequestHeader, transp: StreamTransport,
|
||||
protocol: string = ""): Future[WebSocket] {.async.} =
|
||||
## Creates a new socket from a request.
|
||||
try:
|
||||
if not header.contains("Sec-WebSocket-Version"):
|
||||
raise newException(WebSocketError, "Invalid WebSocket handshake")
|
||||
var ws = WebSocket()
|
||||
ws.masked = false
|
||||
ws.protocol = protocol
|
||||
ws.tcpSocket = transp
|
||||
await ws.handshake(header)
|
||||
return ws
|
||||
except ValueError, KeyError:
|
||||
# Wrap all exceptions in a WebSocketError so its easy to catch.
|
||||
raise newException(
|
||||
WebSocketError,
|
||||
"Failed to create WebSocket from request: " & getCurrentExceptionMsg()
|
||||
)
|
||||
|
||||
type
|
||||
Opcode* = enum
|
||||
## 4 bits. Defines the interpretation of the "Payload data".
|
||||
Cont = 0x0 ## Denotes a continuation frame.
|
||||
Text = 0x1 ## Denotes a text frame.
|
||||
Binary = 0x2 ## Denotes a binary frame.
|
||||
# 3-7 are reserved for further non-control frames.
|
||||
Close = 0x8 ## Denotes a connection close.
|
||||
Ping = 0x9 ## Denotes a ping.
|
||||
Pong = 0xa ## Denotes a pong.
|
||||
# B-F are reserved for further control frames.
|
||||
|
||||
#[
|
||||
+---------------------------------------------------------------+
|
||||
|0 1 2 3 |
|
||||
|0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1|
|
||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
||||
| |1|2|3| |K| | |
|
||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
| Extended payload length continued, if payload len == 127 |
|
||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
||||
| |Masking-key, if MASK set to 1 |
|
||||
+-------------------------------+-------------------------------+
|
||||
| Masking-key (continued) | Payload Data |
|
||||
+-------------------------------- - - - - - - - - - - - - - - - +
|
||||
: Payload Data continued ... :
|
||||
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
| Payload Data continued ... |
|
||||
+---------------------------------------------------------------+
|
||||
]#
|
||||
Frame = tuple
|
||||
fin: bool ## Indicates that this is the final fragment in a message.
|
||||
rsv1: bool ## MUST be 0 unless negotiated that defines meanings
|
||||
rsv2: bool ## MUST be 0
|
||||
rsv3: bool ## MUST be 0
|
||||
opcode: Opcode ## Defines the interpretation of the "Payload data".
|
||||
mask: bool ## Defines whether the "Payload data" is masked.
|
||||
data: string ## Payload data
|
||||
|
||||
proc encodeFrame(f: Frame): string =
|
||||
## Encodes a frame into a string buffer.
|
||||
## See https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
|
||||
var ret = newStringStream()
|
||||
|
||||
var b0 = (f.opcode.uint8 and 0x0f) # 0th byte: opcodes and flags.
|
||||
if f.fin:
|
||||
b0 = b0 or 128u8
|
||||
|
||||
ret.write(b0)
|
||||
|
||||
# Payload length can be 7 bits, 7+16 bits, or 7+64 bits.
|
||||
# 1st byte: payload len start and mask bit.
|
||||
var b1 = 0u8
|
||||
|
||||
if f.data.len <= 125:
|
||||
b1 = f.data.len.uint8
|
||||
elif f.data.len > 125 and f.data.len <= 0xffff:
|
||||
b1 = 126u8
|
||||
else:
|
||||
b1 = 127u8
|
||||
|
||||
if f.mask:
|
||||
b1 = b1 or (1 shl 7)
|
||||
|
||||
ret.write(uint8 b1)
|
||||
|
||||
# Only need more bytes if data len is 7+16 bits, or 7+64 bits.
|
||||
if f.data.len > 125 and f.data.len <= 0xffff:
|
||||
# Data len is 7+16 bits.
|
||||
ret.write(htons(f.data.len.uint16))
|
||||
elif f.data.len > 0xffff:
|
||||
# Data len is 7+64 bits.
|
||||
var len = f.data.len
|
||||
ret.write char((len shr 56) and 255)
|
||||
ret.write char((len shr 48) and 255)
|
||||
ret.write char((len shr 40) and 255)
|
||||
ret.write char((len shr 32) and 255)
|
||||
ret.write char((len shr 24) and 255)
|
||||
ret.write char((len shr 16) and 255)
|
||||
ret.write char((len shr 8) and 255)
|
||||
ret.write char(len and 255)
|
||||
|
||||
var data = f.data
|
||||
|
||||
if f.mask:
|
||||
# If we need to mask it generate random mask key and mask the data.
|
||||
let maskKey = genMaskKey()
|
||||
for i in 0..<data.len:
|
||||
data[i] = (data[i].uint8 xor maskKey[i mod 4].uint8).char
|
||||
# Write mask key next.
|
||||
ret.write(maskKey)
|
||||
|
||||
# Write the data.
|
||||
ret.write(data)
|
||||
ret.setPosition(0)
|
||||
return ret.readAll()
|
||||
|
||||
proc send*(ws: WebSocket, text: string, opcode = Opcode.Text): Future[void] {.async.} =
|
||||
try:
|
||||
var frame = encodeFrame((
|
||||
fin: true,
|
||||
rsv1: false,
|
||||
rsv2: false,
|
||||
rsv3: false,
|
||||
opcode: opcode,
|
||||
mask: ws.masked,
|
||||
data: text
|
||||
))
|
||||
const maxSize = 1024*1024
|
||||
# Send stuff in 1 megabyte chunks to prevent IOErrors.
|
||||
# This really large packets.
|
||||
var i = 0
|
||||
while i < frame.len:
|
||||
let data = frame[i ..< min(frame.len, i + maxSize)]
|
||||
discard await ws.tcpSocket.write(data)
|
||||
i += maxSize
|
||||
await sleepAsync(1)
|
||||
except Defect, IOError, OSError, ValueError:
|
||||
# Wrap all exceptions in a WebSocketError so its easy to catch
|
||||
raise newException(WebSocketError, "Failed to send data: " &
|
||||
getCurrentExceptionMsg())
|
||||
|
||||
proc close*(ws: WebSocket) =
|
||||
## Close the Socket, sends close packet.
|
||||
ws.readyState = Closed
|
||||
proc close() {.async.} =
|
||||
await ws.send("", Close)
|
||||
ws.tcpSocket.close()
|
||||
asyncCheck close()
|
||||
|
||||
proc toString(bytes: seq[byte]): string =
|
||||
result = ""
|
||||
for byte in bytes: result.add(char(byte))
|
||||
|
||||
proc receiveFrame(ws: WebSocket): Future[Frame] {.async.} =
|
||||
## Gets a frame from the WebSocket.
|
||||
## See https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
|
||||
if cast[int](ws.tcpSocket.fd) == -1:
|
||||
ws.readyState = Closed
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
|
||||
# Grab the header.
|
||||
var header: seq[byte]
|
||||
try:
|
||||
header = await ws.tcpSocket.read(2)
|
||||
except:
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
|
||||
if header.len != 2:
|
||||
ws.readyState = Closed
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
|
||||
let b0 = header[0].uint8
|
||||
let b1 = header[1].uint8
|
||||
|
||||
# Read the flags and fin from the header.
|
||||
result.fin = b0[0]
|
||||
result.rsv1 = b0[1]
|
||||
result.rsv2 = b0[2]
|
||||
result.rsv3 = b0[3]
|
||||
result.opcode = (b0 and 0x0f).Opcode
|
||||
|
||||
# If any of the rsv are set close the socket.
|
||||
if result.rsv1 or result.rsv2 or result.rsv3:
|
||||
ws.readyState = Closed
|
||||
raise newException(WebSocketError, "WebSocket rsv mismatch")
|
||||
|
||||
# Payload length can be 7 bits, 7+16 bits, or 7+64 bits.
|
||||
var finalLen: uint = 0
|
||||
|
||||
let headerLen = uint(b1 and 0x7f)
|
||||
if headerLen == 0x7e:
|
||||
# Length must be 7+16 bits.
|
||||
var length = await ws.tcpSocket.read(2)
|
||||
if length.len != 2:
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
finalLen = cast[ptr uint16](length[0].addr)[].htons
|
||||
|
||||
elif headerLen == 0x7f:
|
||||
# Length must be 7+64 bits.
|
||||
var length = await ws.tcpSocket.read(8)
|
||||
if length.len != 8:
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
finalLen = cast[ptr uint32](length[4].addr)[].htonl
|
||||
|
||||
else:
|
||||
# Length must be 7 bits.
|
||||
finalLen = headerLen
|
||||
|
||||
# Do we need to apply mask?
|
||||
result.mask = (b1 and 0x80) == 0x80
|
||||
|
||||
if ws.masked == result.mask:
|
||||
# Server sends unmasked but accepts only masked.
|
||||
# Client sends masked but accepts only unmasked.
|
||||
raise newException(WebSocketError, "Socket mask mismatch")
|
||||
|
||||
var maskKey: seq[byte]
|
||||
if result.mask:
|
||||
# Read the mask.
|
||||
maskKey = await ws.tcpSocket.read(4)
|
||||
if maskKey.len != 4:
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
|
||||
# Read the data.
|
||||
var data: seq[byte]
|
||||
data = await ws.tcpSocket.read(int finalLen)
|
||||
result.data = toString(data)
|
||||
if result.data.len != int finalLen:
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
|
||||
if result.mask:
|
||||
# Apply mask, if we need too.
|
||||
for i in 0 ..< result.data.len:
|
||||
result.data[i] = (result.data[i].uint8 xor maskKey[i mod 4].uint8).char
|
||||
|
||||
|
||||
proc receivePacket*(ws: WebSocket): Future[(Opcode, string)] {.async.} =
|
||||
## Wait for a string or binary packet to come in.
|
||||
var frame = await ws.receiveFrame()
|
||||
result[0] = frame.opcode
|
||||
result[1] = frame.data
|
||||
# If there are more parts read and wait for them
|
||||
while frame.fin != true:
|
||||
frame = await ws.receiveFrame()
|
||||
if frame.opcode != Cont:
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
result[1].add frame.data
|
||||
return
|
||||
|
||||
proc receiveStrPacket*(ws: WebSocket): Future[string] {.async.} =
|
||||
## Wait only for a string packet to come. Errors out on Binary packets.
|
||||
let (opcode, data) = await ws.receivePacket()
|
||||
case opcode:
|
||||
of Text:
|
||||
return data
|
||||
of Binary:
|
||||
raise newException(WebSocketError,
|
||||
"Expected string packet, received binary packet")
|
||||
of Ping:
|
||||
await ws.send(data, Pong)
|
||||
of Pong:
|
||||
discard
|
||||
of Cont:
|
||||
discard
|
||||
of Close:
|
||||
ws.readyState = Closed
|
||||
raise newException(WebSocketError, "Socket closed")
|
||||
|
||||
proc sendHTTPResponse*(transp: StreamTransport, version: HttpVersion, code: HttpCode,
|
||||
data: string = ""): Future[bool] {.async.} =
|
||||
var answer = $version
|
||||
answer.add(" ")
|
||||
answer.add($code)
|
||||
answer.add("\r\n")
|
||||
answer.add("Date: " & httpDate() & "\r\n")
|
||||
if len(data) > 0:
|
||||
answer.add("Content-Type: application/json\r\n")
|
||||
answer.add("Content-Length: " & $len(data) & "\r\n")
|
||||
answer.add("\r\n")
|
||||
if len(data) > 0:
|
||||
answer.add(data)
|
||||
try:
|
||||
let res = await transp.write(answer)
|
||||
if res != len(answer):
|
||||
result = false
|
||||
result = true
|
||||
except:
|
||||
result = false
|
||||
|
||||
proc validateRequest(transp: StreamTransport,
|
||||
header: HttpRequestHeader): Future[ReqStatus] {.async.} =
|
||||
if header.meth notin {MethodGet}:
|
||||
# Request method is either PUT or DELETE.
|
||||
debug "GET method is only allowed", address = transp.remoteAddress()
|
||||
if await transp.sendHTTPResponse(header.version, Http405):
|
||||
result = Error
|
||||
else:
|
||||
result = ErrorFailure
|
||||
return
|
||||
|
||||
if header.contentLength() > MaxHttpRequestSize:
|
||||
# request length is more then `MaxHttpRequestSize`.
|
||||
debug "Maximum size of request body reached",
|
||||
address = transp.remoteAddress()
|
||||
if await transp.sendHTTPResponse(header.version, Http413):
|
||||
result = Error
|
||||
else:
|
||||
result = ErrorFailure
|
||||
return
|
||||
|
||||
result = Success
|
||||
|
||||
proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} =
|
||||
## Process transport data to the RPC server
|
||||
var httpServer = cast[HttpServer](server)
|
||||
var buffer = newSeq[byte](MaxHttpHeadersSize)
|
||||
var header: HttpRequestHeader
|
||||
var connection: string
|
||||
|
||||
info "Received connection", address = $transp.remoteAddress()
|
||||
try:
|
||||
let hlenfut = transp.readUntil(addr buffer[0], MaxHttpHeadersSize, HeadersMark)
|
||||
let ores = await withTimeout(hlenfut, HttpHeadersTimeout)
|
||||
if not ores:
|
||||
# Timeout
|
||||
debug "Timeout expired while receiving headers",
|
||||
address = transp.remoteAddress()
|
||||
let res = await transp.sendHTTPResponse(HttpVersion11, Http408)
|
||||
await transp.closeWait()
|
||||
return
|
||||
else:
|
||||
let hlen = hlenfut.read()
|
||||
buffer.setLen(hlen)
|
||||
header = buffer.parseRequest()
|
||||
if header.failed():
|
||||
# Header could not be parsed
|
||||
debug "Malformed header received",
|
||||
address = transp.remoteAddress()
|
||||
let res = await transp.sendHTTPResponse(HttpVersion11, Http400)
|
||||
await transp.closeWait()
|
||||
return
|
||||
except TransportLimitError:
|
||||
# size of headers exceeds `MaxHttpHeadersSize`
|
||||
debug "Maximum size of headers limit reached",
|
||||
address = transp.remoteAddress()
|
||||
let res = await transp.sendHTTPResponse(HttpVersion11, Http413)
|
||||
await transp.closeWait()
|
||||
return
|
||||
except TransportIncompleteError:
|
||||
# remote peer disconnected
|
||||
debug "Remote peer disconnected", address = transp.remoteAddress()
|
||||
await transp.closeWait()
|
||||
return
|
||||
except TransportOsError as exc:
|
||||
debug "Problems with networking", address = transp.remoteAddress(),
|
||||
error = exc.msg
|
||||
await transp.closeWait()
|
||||
return
|
||||
except CatchableError as exc:
|
||||
debug "Unknown exception", address = transp.remoteAddress(),
|
||||
error = exc.msg
|
||||
await transp.closeWait()
|
||||
return
|
||||
|
||||
let vres = await validateRequest(transp, header)
|
||||
if vres == Success:
|
||||
trace "Received valid RPC request", address = $transp.remoteAddress()
|
||||
|
||||
# Call the user's callback.
|
||||
if httpServer.callback != nil:
|
||||
await httpServer.callback(transp, header)
|
||||
await transp.closeWait()
|
||||
elif vres == ErrorFailure:
|
||||
debug "Remote peer disconnected", address = transp.remoteAddress()
|
||||
await transp.closeWait()
|
||||
|
||||
proc newHttpServer*(address: string, handler: AsyncCallback,
|
||||
flags: set[ServerFlags] = {ReuseAddr}): HttpServer =
|
||||
new result
|
||||
let address = initTAddress(address)
|
||||
result.callback = handler
|
||||
result = cast[HttpServer](createStreamServer(address, serveClient, flags,
|
||||
child = cast[StreamServer](result)))
|
||||
|
||||
func toTitleCase(s: string): string =
|
||||
result = newString(len(s))
|
||||
var upper = true
|
||||
for i in 0..len(s) - 1:
|
||||
result[i] = if upper: toUpperAscii(s[i]) else: toLowerAscii(s[i])
|
||||
upper = s[i] == '-'
|
||||
|
||||
func toCaseInsensitive*(headers: HttpHeaders, s: string): string {.inline.} =
|
||||
return toTitleCase(s)
|
||||
|
||||
func newHttpHeaders*(): HttpHeaders =
|
||||
## Returns a new ``HttpHeaders`` object. if ``titleCase`` is set to true,
|
||||
## headers are passed to the server in title case (e.g. "Content-Length")
|
||||
new result
|
||||
result.table = newTable[string, seq[string]]()
|
||||
|
||||
func newHttpHeaders*(keyValuePairs:
|
||||
openArray[tuple[key: string, val: string]]): HttpHeaders =
|
||||
## Returns a new ``HttpHeaders`` object from an array. if ``titleCase`` is set to true,
|
||||
## headers are passed to the server in title case (e.g. "Content-Length")
|
||||
new result
|
||||
result.table = newTable[string, seq[string]]()
|
||||
|
||||
for pair in keyValuePairs:
|
||||
let key = result.toCaseInsensitive(pair.key)
|
||||
{.cast(noSideEffect).}:
|
||||
if key in result.table:
|
||||
result.table[key].add(pair.val)
|
||||
else:
|
||||
result.table[key] = @[pair.val]
|
||||
|
||||
proc newAsyncWebsocketClient*(uri: Uri, protocols: seq[string] = @[]): Future[WebSocket] {.async.} =
|
||||
let
|
||||
keyDec = align(
|
||||
when declared(toUnix):
|
||||
$getTime().toUnix
|
||||
else:
|
||||
$getTime().toSeconds.int64, 16, '#')
|
||||
key = encode(keyDec)
|
||||
|
||||
var uri = uri
|
||||
case uri.scheme
|
||||
of "ws":
|
||||
uri.scheme = "http"
|
||||
of "wss":
|
||||
uri.scheme = "https"
|
||||
else:
|
||||
raise newException(WebSocketError, "uri scheme has to be " &
|
||||
"'ws' for plaintext or 'wss' for websocket over ssl.")
|
||||
|
||||
var headers = newHttpHeaders({
|
||||
"Connection": "Upgrade",
|
||||
"Upgrade": "websocket",
|
||||
"Cache-Control": "no-cache",
|
||||
"Sec-WebSocket-Version": "13",
|
||||
"Sec-WebSocket-Key": key
|
||||
})
|
||||
|
||||
new(result)
|
||||
|
||||
proc newAsyncWebsocketClient*(host: string, port: Port, path: string,
|
||||
protocols: seq[string] = @[]): Future[WebSocket] =
|
||||
let uri = "ws://" & host & ":" & $port & "/" & path
|
||||
result = newAsyncWebsocketClient(parseUri(uri), protocols)
|
||||
|
Loading…
x
Reference in New Issue
Block a user