mirror of
https://github.com/status-im/nim-websock.git
synced 2025-02-26 00:05:15 +00:00
Add HTTP and websocket client.
This commit is contained in:
parent
fa37c87dd5
commit
350fa94418
89
src/ws.nim
89
src/ws.nim
@ -1,12 +1,13 @@
|
|||||||
import chronos, chronicles, httputils, strutils, base64, std/sha1, random,
|
import chronos, chronicles, httputils, strutils, base64, std/sha1, random,
|
||||||
streams, nativesockets
|
streams, nativesockets, uri, times, chronos.timer, tables
|
||||||
|
|
||||||
const
|
const
|
||||||
MaxHttpHeadersSize = 8192 # maximum size of HTTP headers in octets
|
MaxHttpHeadersSize = 8192 # maximum size of HTTP headers in octets
|
||||||
MaxHttpRequestSize = 128 * 1024 # maximum size of HTTP body in octets
|
MaxHttpRequestSize = 128 * 1024 # maximum size of HTTP body in octets
|
||||||
HttpHeadersTimeout = 120.seconds # timeout for receiving headers (120 sec)
|
HttpHeadersTimeout = timer.seconds(120) # timeout for receiving headers (120 sec)
|
||||||
HttpBodyTimeout = 12.seconds # timeout for receiving body (12 sec)
|
HttpBodyTimeout = timer.seconds(12) # timeout for receiving body (12 sec)
|
||||||
HeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)]
|
HeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)]
|
||||||
|
WebsocketUserAgent* = "nim-ws (https://github.com/status-im/nim-ws)"
|
||||||
|
|
||||||
type
|
type
|
||||||
ReadyState* = enum
|
ReadyState* = enum
|
||||||
@ -22,6 +23,7 @@ type
|
|||||||
protocol*: string
|
protocol*: string
|
||||||
readyState*: ReadyState
|
readyState*: ReadyState
|
||||||
masked*: bool # send masked packets
|
masked*: bool # send masked packets
|
||||||
|
header*: HttpHeaders
|
||||||
|
|
||||||
AsyncCallback = proc (transp: StreamTransport,
|
AsyncCallback = proc (transp: StreamTransport,
|
||||||
header: HttpRequestHeader): Future[void] {.closure, gcsafe.}
|
header: HttpRequestHeader): Future[void] {.closure, gcsafe.}
|
||||||
@ -33,6 +35,21 @@ type
|
|||||||
|
|
||||||
WebSocketError* = object of IOError
|
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 =
|
template `[]`(value: uint8, index: int): bool =
|
||||||
## Get bits from uint8, uint8[2] gets 2nd bit.
|
## Get bits from uint8, uint8[2] gets 2nd bit.
|
||||||
(value and (1 shl (7 - index))) != 0
|
(value and (1 shl (7 - index))) != 0
|
||||||
@ -486,3 +503,69 @@ proc newHttpServer*(address: string, handler: AsyncCallback,
|
|||||||
result.callback = handler
|
result.callback = handler
|
||||||
result = cast[HttpServer](createStreamServer(address, serveClient, flags,
|
result = cast[HttpServer](createStreamServer(address, serveClient, flags,
|
||||||
child = cast[StreamServer](result)))
|
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)
|
||||||
|
|
||||||
|
7
test/client.nim
Normal file
7
test/client.nim
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import ws, nativesockets, chronos
|
||||||
|
|
||||||
|
discard waitFor newAsyncWebsocketClient("localhost", Port(8080), path = "/", protocols = @["myfancyprotocol"])
|
||||||
|
echo "connected"
|
||||||
|
|
||||||
|
runForever()
|
||||||
|
|
@ -8,6 +8,8 @@ requires "nim >= 1.2.6"
|
|||||||
requires "chronos >= 2.5.2 & < 3.0.0"
|
requires "chronos >= 2.5.2 & < 3.0.0"
|
||||||
requires "httputils >= 0.2.0"
|
requires "httputils >= 0.2.0"
|
||||||
requires "chronicles >= 0.10.0"
|
requires "chronicles >= 0.10.0"
|
||||||
|
requires "urlly >= 0.2.0"
|
||||||
|
requires "uri"
|
||||||
|
|
||||||
task lint, "format source files according to the official style guide":
|
task lint, "format source files according to the official style guide":
|
||||||
exec "./lint.nims"
|
exec "./lint.nims"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user