mirror of
https://github.com/status-im/nim-websock.git
synced 2025-02-03 13:14:52 +00:00
WIP: Implement websocket TLS. (#7)
* Update http to use chronos http. * Implement TLS in websocket. * Add webscoket TLS test. * Minor nit. * Add TLS test file. * Update http to use chronos http. (#6) * Update http to use chronos http. * Add stream.nim file. * Address comments. * Fix CI failure. * Minor change. * Address comments. * Fix windows CI failing test. * minor cleanup * spacess * more idiomatic connect * use stew/base10 Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com> * Implement TLS in websocket. * Minor nit. * merge master * wip * Update http to use chronos http. (#6) * Update http to use chronos http. * Add stream.nim file. * Address comments. * Fix CI failure. * Minor change. * Address comments. * Fix windows CI failing test. * minor cleanup * spacess * more idiomatic connect * use stew/base10 Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com> * Update http to use chronos http. * Implement TLS in websocket. * Minor nit. * Update http to use chronos http. (#6) * Update http to use chronos http. * Add stream.nim file. * Address comments. * Fix CI failure. * Minor change. * Address comments. * Fix windows CI failing test. * minor cleanup * spacess * more idiomatic connect * use stew/base10 Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com> * Implement TLS in websocket. * Minor nit. * add testing keys * wip * fix test * wip * remove eth dep and add skipdirs * fix package structure * fix deps * check nim version * Fix CI failure. * Don't call `ws.stream.closeWait()` * always close both ends to complete the sequence * misc * don't fail on close * Fix windows CI. * fix linux x86 builds * use consistent connect pattern * move keys to better place * return dumbResponse * small cleanup Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com>
This commit is contained in:
parent
e4f00698ea
commit
6b76bd8261
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -76,21 +76,21 @@ jobs:
|
||||
if: runner.os == 'Linux' && matrix.target.cpu == 'i386'
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo rm /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
|
||||
sudo apt-get update -qq
|
||||
sudo DEBIAN_FRONTEND='noninteractive' apt-get install \
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib
|
||||
--no-install-recommends -yq gcc-multilib g++-multilib \
|
||||
libssl-dev:i386
|
||||
mkdir -p external/bin
|
||||
cat << EOF > external/bin/gcc
|
||||
#!/bin/bash
|
||||
exec $(which gcc) -m32 -mno-adx "\$@"
|
||||
exec $(which gcc) -m32 "\$@"
|
||||
EOF
|
||||
cat << EOF > external/bin/g++
|
||||
#!/bin/bash
|
||||
exec $(which g++) -m32 -mno-adx "\$@"
|
||||
exec $(which g++) -m32 "\$@"
|
||||
EOF
|
||||
chmod 755 external/bin/gcc external/bin/g++
|
||||
echo "${{ github.workspace }}/external/bin" >> $GITHUB_PATH
|
||||
echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH
|
||||
|
||||
- name: Install build dependencies (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
|
@ -1,4 +1,9 @@
|
||||
import ../src/ws, nativesockets, chronos,chronicles, stew/byteutils
|
||||
import pkg/[
|
||||
chronos,
|
||||
chronicles,
|
||||
stew/byteutils]
|
||||
|
||||
import ../ws/ws
|
||||
|
||||
proc main() {.async.} =
|
||||
let ws = await WebSocket.connect(
|
||||
|
@ -3,7 +3,8 @@
|
||||
chronicles,
|
||||
httputils,
|
||||
stew/byteutils]
|
||||
import ../src/ws
|
||||
|
||||
import ../ws/ws
|
||||
|
||||
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isOk():
|
||||
@ -28,7 +29,7 @@ proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
debug "Client Response: ", size = recvData.len
|
||||
await ws.send(recvData)
|
||||
# await ws.close()
|
||||
|
||||
|
||||
except WebSocketError as exc:
|
||||
error "WebSocket error:", exception = exc.msg
|
||||
discard await request.respond(Http200, "Hello World")
|
||||
@ -41,8 +42,8 @@ when isMainModule:
|
||||
let res = HttpServerRef.new(
|
||||
address, process,
|
||||
socketFlags = socketFlags)
|
||||
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
info "Server listening at ", data = address
|
||||
waitFor server.join()
|
||||
waitFor server.join()
|
||||
|
34
examples/tlsclient.nim
Normal file
34
examples/tlsclient.nim
Normal file
@ -0,0 +1,34 @@
|
||||
import pkg/[chronos,
|
||||
chronos/streams/tlsstream,
|
||||
chronicles,
|
||||
stew/byteutils]
|
||||
|
||||
import ../ws/ws
|
||||
|
||||
proc main() {.async.} =
|
||||
let ws = await WebSocket.tlsConnect(
|
||||
"127.0.0.1",
|
||||
Port(8888),
|
||||
path = "/wss",
|
||||
protocols = @["myfancyprotocol"],
|
||||
flags = {NoVerifyHost,NoVerifyServerName})
|
||||
debug "Websocket client: ", State = ws.readyState
|
||||
|
||||
let reqData = "Hello Server"
|
||||
try:
|
||||
echo "sending client "
|
||||
await ws.send(reqData)
|
||||
let buff = await ws.recv()
|
||||
if buff.len <= 0:
|
||||
break
|
||||
let dataStr = string.fromBytes(buff)
|
||||
debug "Server:", data = dataStr
|
||||
|
||||
assert dataStr == reqData
|
||||
return # bail out
|
||||
except WebSocketError as exc:
|
||||
error "WebSocket error:", exception = exc.msg
|
||||
|
||||
# close the websocket
|
||||
await ws.close()
|
||||
waitFor(main())
|
57
examples/tlsserver.nim
Normal file
57
examples/tlsserver.nim
Normal file
@ -0,0 +1,57 @@
|
||||
import pkg/[chronos,
|
||||
chronos/apps/http/shttpserver,
|
||||
chronicles,
|
||||
httputils,
|
||||
stew/byteutils]
|
||||
|
||||
import ../ws/ws
|
||||
import ../tests/keys
|
||||
|
||||
let secureKey = TLSPrivateKey.init(SecureKey)
|
||||
let secureCert = TLSCertificate.init(SecureCert)
|
||||
|
||||
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isOk():
|
||||
let request = r.get()
|
||||
|
||||
debug "Handling request:", uri = request.uri.path
|
||||
if request.uri.path == "/wss":
|
||||
debug "Initiating web socket connection."
|
||||
try:
|
||||
var ws = await createServer(request, "myfancyprotocol")
|
||||
if ws.readyState != Open:
|
||||
error "Failed to open websocket connection."
|
||||
return
|
||||
debug "Websocket handshake completed."
|
||||
# Only reads header for data frame.
|
||||
echo "receiving server "
|
||||
let recvData = await ws.recv()
|
||||
if recvData.len <= 0:
|
||||
debug "Empty messages"
|
||||
break
|
||||
|
||||
if ws.readyState == ReadyState.Closed:
|
||||
return
|
||||
debug "Response: ", data = string.fromBytes(recvData)
|
||||
await ws.send(recvData)
|
||||
except WebSocketError:
|
||||
error "WebSocket error:", exception = getCurrentExceptionMsg()
|
||||
discard await request.respond(Http200, "Hello World")
|
||||
else:
|
||||
return dumbResponse()
|
||||
|
||||
when isMainModule:
|
||||
let address = initTAddress("127.0.0.1:8888")
|
||||
let serverFlags = {Secure, NotifyDisconnect}
|
||||
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
let res = SecureHttpServerRef.new(
|
||||
address, process,
|
||||
serverFlags = serverFlags,
|
||||
socketFlags = socketFlags,
|
||||
tlsPrivateKey = secureKey,
|
||||
tlsCertificate = secureCert)
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
info "Server listening at ", data = address
|
||||
waitFor server.join()
|
55
tests/keys.nim
Normal file
55
tests/keys.nim
Normal file
@ -0,0 +1,55 @@
|
||||
const
|
||||
SecureKey* = """
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCdNv0SX02aeZ4/
|
||||
Yc+p/Kwd5UVOHlpmK7/TVC/kcjFbdoUuKNn8pnX/fyhgSKpUYut+te7YRiZhqlaL
|
||||
EZKjfy8GBZwXZnJCevFkTvGTTebXXExLIsLGfJqKeLAdFCQkX8wV3jV1DT5JLV+D
|
||||
5+HWaiiBr38gsl4ZbfyedTF40JvzokCmcdlx9bpzX1j/b84L/zSwUyyEcgp5G28F
|
||||
Jh5TnxAeDHJpOVjr8XMb/xoNqiDF6NwF96hvOZC14mZ1TxxW5bUzXprsy0l52pmh
|
||||
dN3Crz11+t2h519hRKHxT6/l5pTx/+dApXiP6hMV04CQJNnas3NyRxTDR9dNel+3
|
||||
+wD7/PRTAgMBAAECggEBAJuXPEbegxMKog7gYoE9S6oaqchySc0sJyCjBPL2ANsg
|
||||
JRZV38cnh0hhNDh2MfxqGd7Bd6wbYQjvZ88iiRm+WW+ARcby4MnimtxHNNYwFvG0
|
||||
qt0BffqqftfkMYfV0x8coAJUdFtvy+DoQstsxhlJ3uTaJtrZLD/GlmjMWzXSX0Vy
|
||||
FXiLDO7/LoSjsjaf4e4aLofIyLJS3H1T+5cr/d2mdpRzkeWkxShODsK4cRLOlZ5I
|
||||
pz4Wm2770DTbiYph8ixl/CnmYn6T7V0F5VYujALknipUBeQY4e/A9vrQ/pvqJV+W
|
||||
JjFUne6Rxg/lJjh8vNJp2bK1ZbzpwmZLaZIoEz8t/qECgYEAzvCCA48uQPaurSQ3
|
||||
cvHDhcVwYmEaH8MW8aIW/5l8XJK60GsUHPFhEsfD/ObI5PJJ9aOqgabpRHkvD4ZY
|
||||
a8QJBxCy6UeogUeKvGks8VQ34SZXLimmgrL9Mlljv0v9PloEkVYbztYyX4GVO0ov
|
||||
3oH+hKO+/MclzNDyeXZx3Vv4K+UCgYEAwnyb7tqp7fRqm/8EymIZV5pa0p6h609p
|
||||
EhCBi9ii6d/ewEjsBhs7bPDBO4PO9ylvOvryYZH1hVbQja2anOCBjO8dAHRHWM86
|
||||
964TFriywBQkYxp6dsB8nUjLBDza2xAM3m+OGi9/ATuhEAe5sXp/fZL3tkfSaOXI
|
||||
A7Gzro+kS9cCgYEAtKScSfEeBlWQa9H2mV9UN5z/mtF61YkeqTW+b8cTGVh4vWEL
|
||||
wKww+gzqGAV6Duk2CLijKeSDMmO64gl7fC83VjSMiTklbhz+jbQeKFhFI0Sty71N
|
||||
/j+y6NXBTgdOfLRl0lzhj2/JrzdWBtie6tR9UloCaXSKmb04PTFY+kvDWsUCgYBR
|
||||
krJUnKJpi/qrM2tu93Zpp/QwIxkG+We4i/PKFDNApQVo4S0d4o4qQ1DJBZ/pSxe8
|
||||
RUUkZ3PzWVZgFlCjPAcadbBUYHEMbt7sw7Z98ToIFmqspo53AIVD8yQzwtKIz1KW
|
||||
eXPAx+sdOUV008ivCBIxOVNswPMfzED4S7Bxpw3iQQKBgGJhct2nBsgu0l2/wzh9
|
||||
tpKbalW1RllgptNQzjuBEZMTvPF0L+7BE09/exKtt4N9s3yAzi8o6Qo7RHX5djVc
|
||||
SNgafV4jj7jt2Ilh6KOy9dshtLoEkS1NmiqfVe2go2auXZdyGm+I2yzKWdKGDO0J
|
||||
diTtYf1sA0PgNXdSyDC03TZl
|
||||
-----END PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
SecureCert* = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUe9fr78Dz9PedQ5Sq0uluMWQhX9wwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCSU4xEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTAzMTcwOTMzMzZaFw0zMTAz
|
||||
MTUwOTMzMzZaMEUxCzAJBgNVBAYTAklOMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCdNv0SX02aeZ4/Yc+p/Kwd5UVOHlpmK7/TVC/kcjFb
|
||||
doUuKNn8pnX/fyhgSKpUYut+te7YRiZhqlaLEZKjfy8GBZwXZnJCevFkTvGTTebX
|
||||
XExLIsLGfJqKeLAdFCQkX8wV3jV1DT5JLV+D5+HWaiiBr38gsl4ZbfyedTF40Jvz
|
||||
okCmcdlx9bpzX1j/b84L/zSwUyyEcgp5G28FJh5TnxAeDHJpOVjr8XMb/xoNqiDF
|
||||
6NwF96hvOZC14mZ1TxxW5bUzXprsy0l52pmhdN3Crz11+t2h519hRKHxT6/l5pTx
|
||||
/+dApXiP6hMV04CQJNnas3NyRxTDR9dNel+3+wD7/PRTAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBRkSY1AkGUpVNxG5fYocfgFODtQmTAfBgNVHSMEGDAWgBRkSY1AkGUpVNxG
|
||||
5fYocfgFODtQmTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBt
|
||||
D71VH7F8GOQXITFXCrHwEq1Fx3ScuSnL04NJrXw/e9huzLVQOchAYp/EIn4x2utN
|
||||
S31dt94wvi/IysOVbR1LatYNF5kKgGj2Wc6DH0PswBMk8R1G8QMeCz+hCjf1VDHe
|
||||
AAW1x2q20rJAvUrT6cRBQqeiMzQj0OaJbvfnd2hu0/d0DFkcuGVgBa2zlbG5rbdU
|
||||
Jnq7MQfSaZHd0uBgiKkS+Zw6XaYfWfByCAGSnUqRdOChiJ2stFVLvu+9oQ+PJjJt
|
||||
Er1u9bKTUyeuYpqXr2BP9dqphwu8R4NFVUg6DIRpMFMsybaL7KAd4hD22RXCvc0m
|
||||
uLu7KODi+eW62MHqs4N2
|
||||
-----END CERTIFICATE-----
|
||||
"""
|
@ -1,2 +1,3 @@
|
||||
import ./testframes
|
||||
import ./testwebsockets
|
||||
import ./testtlswebsockets
|
||||
|
@ -1,7 +1,7 @@
|
||||
import unittest
|
||||
|
||||
include ../src/ws
|
||||
include ../src/random
|
||||
include ../ws/ws
|
||||
include ../ws/random
|
||||
|
||||
# TODO: Fix Test.
|
||||
|
||||
|
225
tests/testtlswebsockets.nim
Normal file
225
tests/testtlswebsockets.nim
Normal file
@ -0,0 +1,225 @@
|
||||
import std/strutils, httputils
|
||||
|
||||
import pkg/[asynctest,
|
||||
chronos,
|
||||
chronos/apps/http/shttpserver,
|
||||
stew/byteutils]
|
||||
|
||||
import ../ws/[ws, stream],
|
||||
../examples/tlsserver
|
||||
|
||||
import ./keys
|
||||
|
||||
var server: SecureHttpServerRef
|
||||
let address = initTAddress("127.0.0.1:8888")
|
||||
let serverFlags = {HttpServerFlags.Secure, HttpServerFlags.NotifyDisconnect}
|
||||
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
let clientFlags = {NoVerifyHost, NoVerifyServerName}
|
||||
|
||||
let secureKey = TLSPrivateKey.init(SecureKey)
|
||||
let secureCert = TLSCertificate.init(SecureCert)
|
||||
|
||||
suite "Test websocket TLS handshake":
|
||||
teardown:
|
||||
await server.closeWait()
|
||||
|
||||
test "Test for websocket TLS incorrect protocol":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/wss"
|
||||
expect WSProtoMismatchError:
|
||||
var ws = await createServer(request, "proto")
|
||||
check ws.readyState == ReadyState.Closed
|
||||
|
||||
return await request.respond(Http200, "Connection established")
|
||||
|
||||
let res = SecureHttpServerRef.new(
|
||||
address, cb,
|
||||
serverFlags = serverFlags,
|
||||
socketFlags = socketFlags,
|
||||
tlsPrivateKey = secureKey,
|
||||
tlsCertificate = secureCert)
|
||||
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
expect WSFailedUpgradeError:
|
||||
discard await WebSocket.tlsConnect(
|
||||
"127.0.0.1",
|
||||
Port(8888),
|
||||
path = "/wss",
|
||||
protocols = @["wrongproto"],
|
||||
clientFlags)
|
||||
|
||||
test "Test for websocket TLS incorrect version":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/wss"
|
||||
expect WSVersionError:
|
||||
var ws = await createServer(request, "proto")
|
||||
check ws.readyState == ReadyState.Closed
|
||||
|
||||
return await request.respond(Http200, "Connection established")
|
||||
|
||||
let res = SecureHttpServerRef.new(
|
||||
address, cb,
|
||||
serverFlags = serverFlags,
|
||||
socketFlags = socketFlags,
|
||||
tlsPrivateKey = secureKey,
|
||||
tlsCertificate = secureCert)
|
||||
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
expect WSFailedUpgradeError:
|
||||
discard await WebSocket.tlsConnect(
|
||||
"127.0.0.1",
|
||||
Port(8888),
|
||||
path = "/wss",
|
||||
protocols = @["wrongproto"],
|
||||
clientFlags,
|
||||
version = 14)
|
||||
|
||||
test "Test for websocket TLS client headers":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
let request = r.get()
|
||||
check request.uri.path == "/wss"
|
||||
check request.headers.getString("Connection").toUpperAscii() == "Upgrade".toUpperAscii()
|
||||
check request.headers.getString("Upgrade").toUpperAscii() == "websocket".toUpperAscii()
|
||||
check request.headers.getString("Cache-Control").toUpperAscii() == "no-cache".toUpperAscii()
|
||||
check request.headers.getString("Sec-WebSocket-Version") == $WSDefaultVersion
|
||||
|
||||
check request.headers.contains("Sec-WebSocket-Key")
|
||||
|
||||
discard await request.respond( Http200,"Connection established")
|
||||
|
||||
let res = SecureHttpServerRef.new(
|
||||
address, cb,
|
||||
serverFlags = serverFlags,
|
||||
socketFlags = socketFlags,
|
||||
tlsPrivateKey = secureKey,
|
||||
tlsCertificate = secureCert)
|
||||
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
expect WSFailedUpgradeError:
|
||||
discard await WebSocket.tlsConnect(
|
||||
"127.0.0.1",
|
||||
Port(8888),
|
||||
path = "/wss",
|
||||
protocols = @["proto"],
|
||||
clientFlags)
|
||||
|
||||
suite "Test websocket TLS transmission":
|
||||
teardown:
|
||||
await server.closeWait()
|
||||
|
||||
test "Server - test reading simple frame":
|
||||
let testString = "Hello!"
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/wss"
|
||||
let ws = await createServer(request, "proto")
|
||||
let servRes = await ws.recv()
|
||||
check string.fromBytes(servRes) == testString
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = SecureHttpServerRef.new(
|
||||
address, cb,
|
||||
serverFlags = serverFlags,
|
||||
socketFlags = socketFlags,
|
||||
tlsPrivateKey = secureKey,
|
||||
tlsCertificate = secureCert)
|
||||
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
let wsClient = await WebSocket.tlsConnect(
|
||||
"127.0.0.1",
|
||||
Port(8888),
|
||||
path = "/wss",
|
||||
protocols = @["proto"],
|
||||
clientFlags)
|
||||
|
||||
await wsClient.send(testString)
|
||||
await wsClient.close()
|
||||
|
||||
test "Client - test reading simple frame":
|
||||
let testString = "Hello!"
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/wss"
|
||||
let ws = await createServer(request, "proto")
|
||||
let servRes = await ws.recv()
|
||||
check string.fromBytes(servRes) == testString
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = SecureHttpServerRef.new(
|
||||
address, cb,
|
||||
serverFlags = serverFlags,
|
||||
socketFlags = socketFlags,
|
||||
tlsPrivateKey = secureKey,
|
||||
tlsCertificate = secureCert)
|
||||
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
let wsClient = await WebSocket.tlsConnect(
|
||||
"127.0.0.1",
|
||||
Port(8888),
|
||||
path = "/wss",
|
||||
protocols = @["proto"],
|
||||
clientFlags)
|
||||
|
||||
await wsClient.send(testString)
|
||||
await wsClient.close()
|
||||
|
||||
test "Client - test reading simple frame":
|
||||
let testString = "Hello!"
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/wss"
|
||||
let ws = await createServer(request, "proto")
|
||||
await ws.send(testString)
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = SecureHttpServerRef.new(
|
||||
address, cb,
|
||||
serverFlags = serverFlags,
|
||||
socketFlags = socketFlags,
|
||||
tlsPrivateKey = secureKey,
|
||||
tlsCertificate = secureCert)
|
||||
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
let wsClient = await WebSocket.tlsConnect(
|
||||
"127.0.0.1",
|
||||
Port(8888),
|
||||
path = "/wss",
|
||||
protocols = @["proto"],
|
||||
clientFlags)
|
||||
|
||||
var clientRes = await wsClient.recv()
|
||||
check string.fromBytes(clientRes) == testString
|
||||
await wsClient.close()
|
@ -4,26 +4,29 @@ import pkg/[asynctest,
|
||||
chronos,
|
||||
chronos/apps/http/httpserver,
|
||||
stew/byteutils]
|
||||
import ../src/ws, ../src/stream
|
||||
|
||||
import ../ws/[ws, stream]
|
||||
|
||||
var server: HttpServerRef
|
||||
let address = initTAddress("127.0.0.1:8888")
|
||||
|
||||
suite "Test handshake":
|
||||
teardown:
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
|
||||
test "Test for incorrect protocol":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
expect WSProtoMismatchError:
|
||||
var ws = await createServer(request, "proto")
|
||||
check ws.readyState == ReadyState.Closed
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -36,15 +39,16 @@ suite "Test handshake":
|
||||
|
||||
test "Test for incorrect version":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
expect WSVersionError:
|
||||
var ws = await createServer(request, "proto")
|
||||
check ws.readyState == ReadyState.Closed
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -58,7 +62,9 @@ suite "Test handshake":
|
||||
|
||||
test "Test for client headers":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
check request.headers.getString("Connection").toUpperAscii() == "Upgrade".toUpperAscii()
|
||||
@ -68,8 +74,7 @@ suite "Test handshake":
|
||||
|
||||
check request.headers.contains("Sec-WebSocket-Key")
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -80,23 +85,49 @@ suite "Test handshake":
|
||||
path = "/ws",
|
||||
protocols = @["proto"])
|
||||
|
||||
test "Test for incorrect scheme":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
|
||||
expect WSProtoMismatchError:
|
||||
var ws = await createServer(request, "proto")
|
||||
check ws.readyState == ReadyState.Closed
|
||||
|
||||
return await request.respond(Http200, "Connection established")
|
||||
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
let uri = "wx://127.0.0.1:8888/ws"
|
||||
expect WSWrongUriSchemeError:
|
||||
discard await WebSocket.connect(
|
||||
parseUri(uri),
|
||||
protocols = @["proto"])
|
||||
|
||||
suite "Test transmission":
|
||||
teardown:
|
||||
await server.closeWait()
|
||||
|
||||
test "Server - test reading simple frame":
|
||||
let testString = "Hello!"
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(request, "proto")
|
||||
let servRes = await ws.recv()
|
||||
|
||||
check string.fromBytes(servRes) == testString
|
||||
await ws.stream.closeWait()
|
||||
await ws.close()
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -106,18 +137,21 @@ suite "Test transmission":
|
||||
path = "/ws",
|
||||
protocols = @["proto"])
|
||||
await wsClient.send(testString)
|
||||
await wsClient.close()
|
||||
|
||||
test "Client - test reading simple frame":
|
||||
let testString = "Hello!"
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(request, "proto")
|
||||
await ws.send(testString)
|
||||
await ws.stream.closeWait()
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
await ws.close()
|
||||
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -128,6 +162,7 @@ suite "Test transmission":
|
||||
protocols = @["proto"])
|
||||
|
||||
var clientRes = await wsClient.recv()
|
||||
await wsClient.close()
|
||||
check string.fromBytes(clientRes) == testString
|
||||
|
||||
suite "Test ping-pong":
|
||||
@ -137,7 +172,9 @@ suite "Test ping-pong":
|
||||
test "Server - test ping-pong control messages":
|
||||
var ping, pong = false
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(
|
||||
@ -146,11 +183,11 @@ suite "Test ping-pong":
|
||||
onPong = proc() =
|
||||
pong = true
|
||||
)
|
||||
|
||||
await ws.ping()
|
||||
await ws.close()
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -171,7 +208,9 @@ suite "Test ping-pong":
|
||||
test "Client - test ping-pong control messages":
|
||||
var ping, pong = false
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(
|
||||
@ -182,8 +221,9 @@ suite "Test ping-pong":
|
||||
)
|
||||
|
||||
discard await ws.recv()
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
await ws.close()
|
||||
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -208,13 +248,14 @@ suite "Test framing":
|
||||
|
||||
test "should split message into frames":
|
||||
let testString = "1234567890"
|
||||
var done = newFuture[void]()
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef]{.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(request, "proto")
|
||||
|
||||
let ws = await createServer(request, "proto")
|
||||
let frame1 = await ws.readFrame()
|
||||
check not isNil(frame1)
|
||||
var data1 = newSeq[byte](frame1.remainder().int)
|
||||
@ -227,11 +268,10 @@ suite "Test framing":
|
||||
let read2 = await ws.stream.reader.readOnce(addr data2[0], data2.len)
|
||||
check read2 == 5
|
||||
|
||||
await ws.stream.closeWait()
|
||||
done.complete()
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -243,20 +283,22 @@ suite "Test framing":
|
||||
frameSize = 5)
|
||||
|
||||
await wsClient.send(testString)
|
||||
await done
|
||||
await wsClient.close()
|
||||
|
||||
test "should fail to read past max message size":
|
||||
let testString = "1234567890"
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async, gcsafe.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(request, "proto")
|
||||
await ws.send(testString)
|
||||
await ws.stream.closeWait()
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -269,19 +311,24 @@ suite "Test framing":
|
||||
expect WSMaxMessageSizeError:
|
||||
discard await wsClient.recv(5)
|
||||
|
||||
await wsClient.close()
|
||||
|
||||
suite "Test Closing":
|
||||
teardown:
|
||||
await server.closeWait()
|
||||
|
||||
test "Server closing":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(request, "proto")
|
||||
await ws.close()
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
return dumbResponse()
|
||||
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -296,12 +343,18 @@ suite "Test Closing":
|
||||
|
||||
test "Server closing with status":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
proc closeServer(status: Status, reason: string): CloseResult {.gcsafe.} =
|
||||
check status == Status.TooLarge
|
||||
check reason == "Message too big!"
|
||||
proc closeServer(status: Status, reason: string): CloseResult
|
||||
{.gcsafe, raises: [Defect].} =
|
||||
try:
|
||||
check status == Status.TooLarge
|
||||
check reason == "Message too big!"
|
||||
except Exception as exc:
|
||||
raise newException(Defect, exc.msg)
|
||||
|
||||
return (Status.Fulfilled, "")
|
||||
|
||||
@ -311,15 +364,19 @@ suite "Test Closing":
|
||||
onClose = closeServer)
|
||||
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
proc clientClose(status: Status, reason: string): CloseResult {.gcsafe.} =
|
||||
check status == Status.Fulfilled
|
||||
return (Status.TooLarge, "Message too big!")
|
||||
proc clientClose(status: Status, reason: string): CloseResult
|
||||
{.gcsafe, raises: [Defect].} =
|
||||
try:
|
||||
check status == Status.Fulfilled
|
||||
return (Status.TooLarge, "Message too big!")
|
||||
except Exception as exc:
|
||||
raise newException(Defect, exc.msg)
|
||||
|
||||
let wsClient = await WebSocket.connect(
|
||||
"127.0.0.1",
|
||||
@ -333,14 +390,17 @@ suite "Test Closing":
|
||||
|
||||
test "Client closing":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
let ws = await createServer(request, "proto")
|
||||
discard await ws.recv()
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
@ -353,28 +413,39 @@ suite "Test Closing":
|
||||
|
||||
test "Client closing with status":
|
||||
proc cb(r: RequestFence): Future[HttpResponseRef] {.async, gcsafe.} =
|
||||
check r.isOk()
|
||||
if r.isErr():
|
||||
return dumbResponse()
|
||||
|
||||
let request = r.get()
|
||||
check request.uri.path == "/ws"
|
||||
proc closeServer(status: Status, reason: string): CloseResult {.gcsafe.} =
|
||||
check status == Status.Fulfilled
|
||||
return (Status.TooLarge, "Message too big!")
|
||||
proc closeServer(status: Status, reason: string): CloseResult
|
||||
{.gcsafe, raises: [Defect].} =
|
||||
try:
|
||||
check status == Status.Fulfilled
|
||||
return (Status.TooLarge, "Message too big!")
|
||||
except Exception as exc:
|
||||
raise newException(Defect, exc.msg)
|
||||
|
||||
let ws = await createServer(
|
||||
request,
|
||||
"proto",
|
||||
onClose = closeServer)
|
||||
discard await ws.recv()
|
||||
await ws.close()
|
||||
return dumbResponse()
|
||||
|
||||
let res = HttpServerRef.new(
|
||||
address, cb)
|
||||
let res = HttpServerRef.new(address, cb)
|
||||
server = res.get()
|
||||
server.start()
|
||||
|
||||
proc clientClose(status: Status, reason: string): CloseResult {.gcsafe.} =
|
||||
check status == Status.TooLarge
|
||||
check reason == "Message too big!"
|
||||
return (Status.Fulfilled, "")
|
||||
proc clientClose(status: Status, reason: string): CloseResult
|
||||
{.gcsafe, raises: [Defect].} =
|
||||
try:
|
||||
check status == Status.TooLarge
|
||||
check reason == "Message too big!"
|
||||
return (Status.Fulfilled, "")
|
||||
except Exception as exc:
|
||||
raise newException(Defect, exc.msg)
|
||||
|
||||
let wsClient = await WebSocket.connect(
|
||||
"127.0.0.1",
|
||||
|
10
ws.nimble
10
ws.nimble
@ -3,16 +3,20 @@ version = "0.1.0"
|
||||
author = "Status Research & Development GmbH"
|
||||
description = "WS protocol implementation"
|
||||
license = "MIT"
|
||||
skipDirs = @["examples", "test"]
|
||||
|
||||
requires "nim == 1.2.6"
|
||||
requires "nim >= 1.2.6"
|
||||
requires "chronos >= 2.5.2"
|
||||
requires "httputils >= 0.2.0"
|
||||
requires "chronicles >= 0.10.0"
|
||||
requires "urlly >= 0.2.0"
|
||||
requires "stew >= 0.1.0"
|
||||
requires "eth"
|
||||
requires "asynctest >= 0.2.0 & < 0.3.0"
|
||||
requires "nimcrypto"
|
||||
requires "bearssl"
|
||||
|
||||
task test, "run tests":
|
||||
exec "nim c -r --opt:speed -d:debug --verbosity:0 --hints:off ./tests/testall.nim"
|
||||
rmFile "./tests/testall"
|
||||
rmFile "./tests/testwebsockets"
|
||||
rmFile "./tests/testframes"
|
||||
rmFile "./tests/testtlswebsockets"
|
||||
|
@ -1,8 +1,23 @@
|
||||
import bearssl
|
||||
export bearssl
|
||||
|
||||
## Random helpers: similar as in stdlib, but with BrHmacDrbgContext rng
|
||||
const randMax = 18_446_744_073_709_551_615'u64
|
||||
|
||||
proc newRng*(): ref BrHmacDrbgContext =
|
||||
# You should only create one instance of the RNG per application / library
|
||||
# Ref is used so that it can be shared between components
|
||||
# TODO consider moving to bearssl
|
||||
var seeder = brPrngSeederSystem(nil)
|
||||
if seeder == nil:
|
||||
return nil
|
||||
|
||||
var rng = (ref BrHmacDrbgContext)()
|
||||
brHmacDrbgInit(addr rng[], addr sha256Vtable, nil, 0)
|
||||
if seeder(addr rng.vtable) == 0:
|
||||
return nil
|
||||
rng
|
||||
|
||||
proc rand*(rng: var BrHmacDrbgContext, max: Natural): int =
|
||||
if max == 0: return 0
|
||||
var x: uint64
|
@ -6,9 +6,9 @@
|
||||
import strutils
|
||||
|
||||
const
|
||||
HttpHeadersTimeout = timer.seconds(120) # timeout for receiving headers (120 sec)
|
||||
HeaderSep = @[byte('\c'), byte('\L'), byte('\c'), byte('\L')]
|
||||
MaxHttpHeadersSize = 8192 # maximum size of HTTP headers in octets
|
||||
HttpHeadersTimeout = timer.seconds(120) # timeout for receiving headers (120 sec)
|
||||
MaxHttpHeadersSize = 8192 # maximum size of HTTP headers in octets
|
||||
|
||||
proc readHeaders*(rstream: AsyncStreamReader): Future[seq[byte]] {.async.} =
|
||||
var buffer = newSeq[byte](MaxHttpHeadersSize)
|
||||
@ -42,12 +42,12 @@ proc readHeaders*(rstream: AsyncStreamReader): Future[seq[byte]] {.async.} =
|
||||
|
||||
if error:
|
||||
buffer.setLen(0)
|
||||
|
||||
return buffer
|
||||
|
||||
proc closeWait*(wsStream : AsyncStream): Future[void] {.async.} =
|
||||
if not wsStream.writer.tsource.closed():
|
||||
await wsStream.writer.tsource.closeWait()
|
||||
if not wsStream.reader.tsource.closed():
|
||||
await wsStream.reader.tsource.closeWait()
|
||||
proc closeWait*(wsStream : AsyncStream) {.async.} =
|
||||
await allFutures(
|
||||
wsStream.writer.tsource.closeWait(),
|
||||
wsStream.reader.tsource.closeWait())
|
||||
|
||||
# TODO: Implement stream read and write wrapper.
|
@ -1,3 +1,5 @@
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import std/[tables,
|
||||
strutils,
|
||||
uri,
|
||||
@ -7,13 +9,13 @@ import pkg/[chronos,
|
||||
chronos/apps/http/httptable,
|
||||
chronos/apps/http/httpserver,
|
||||
chronos/streams/asyncstream,
|
||||
chronos/streams/tlsstream,
|
||||
chronicles,
|
||||
httputils,
|
||||
stew/byteutils,
|
||||
stew/endians2,
|
||||
stew/base64,
|
||||
stew/base10,
|
||||
eth/keys,
|
||||
nimcrypto/sha]
|
||||
|
||||
import ./random, ./stream
|
||||
@ -125,14 +127,14 @@ type
|
||||
length: uint64 ## Message size.
|
||||
consumed: uint64 ## how much has been consumed from the frame
|
||||
|
||||
ControlCb* = proc() {.gcsafe.}
|
||||
ControlCb* = proc() {.gcsafe, raises: [Defect].}
|
||||
|
||||
CloseResult* = tuple
|
||||
code: Status
|
||||
reason: string
|
||||
|
||||
CloseCb* = proc(code: Status, reason: string):
|
||||
CloseResult {.gcsafe.}
|
||||
CloseResult {.gcsafe, raises: [Defect].}
|
||||
|
||||
WebSocket* = ref object
|
||||
stream*: AsyncStream
|
||||
@ -208,9 +210,11 @@ proc handshake*(
|
||||
|
||||
let cKey = ws.key & WSGuid
|
||||
let acceptKey = Base64Pad.encode(sha1.digest(cKey.toOpenArray(0, cKey.high)).data)
|
||||
var headerData = [
|
||||
("Connection", "Upgrade"),
|
||||
("Upgrade", "webSocket" ),
|
||||
("Sec-WebSocket-Accept", acceptKey)]
|
||||
|
||||
var headerData = [("Connection", "Upgrade"),("Upgrade", "webSocket" ),
|
||||
("Sec-WebSocket-Accept", acceptKey)]
|
||||
var headers = HttpTable.init(headerData)
|
||||
if ws.protocol != "":
|
||||
headers.add("Sec-WebSocket-Protocol", ws.protocol)
|
||||
@ -404,7 +408,7 @@ proc handleControl*(ws: WebSocket, frame: Frame) {.async.} =
|
||||
|
||||
if frame.length > 125:
|
||||
raise newException(WSPayloadTooLarge,
|
||||
"Control message payload is freater than 125 bytes!")
|
||||
"Control message payload is greater than 125 bytes!")
|
||||
|
||||
try:
|
||||
# Process control frame payload.
|
||||
@ -439,7 +443,7 @@ proc readFrame*(ws: WebSocket): Future[Frame] {.async.} =
|
||||
##
|
||||
|
||||
try:
|
||||
while ws.readyState != ReadyState.Closed: # read until a data frame arrives
|
||||
while ws.readyState != ReadyState.Closed:
|
||||
# Grab the header.
|
||||
var header = newSeq[byte](2)
|
||||
await ws.stream.reader.readExactly(addr header[0], 2)
|
||||
@ -525,7 +529,7 @@ proc recv*(
|
||||
size: int): Future[int] {.async.} =
|
||||
## Attempts to read up to `size` bytes
|
||||
##
|
||||
## Will read as many frames as necesary
|
||||
## Will read as many frames as necessary
|
||||
## to fill the buffer until either
|
||||
## the message ends (frame.fin) or
|
||||
## the buffer is full. If no data is on
|
||||
@ -643,7 +647,8 @@ proc close*(
|
||||
proc initiateHandshake(
|
||||
uri: Uri,
|
||||
address: TransportAddress,
|
||||
headers: HttpTable): Future[AsyncStream] {.async.} =
|
||||
headers: HttpTable,
|
||||
flags: set[TLSFlags] = {}): Future[AsyncStream] {.async.} =
|
||||
## Initiate handshake with server
|
||||
|
||||
var transp: StreamTransport
|
||||
@ -654,11 +659,27 @@ proc initiateHandshake(
|
||||
TransportError,
|
||||
"Cannot connect to " & $transp.remoteAddress() & " Error: " & exc.msg)
|
||||
|
||||
let requestHeader = "GET " & uri.path & " HTTP/1.1" & CRLF & $headers
|
||||
let reader = newAsyncStreamReader(transp)
|
||||
let writer = newAsyncStreamWriter(transp)
|
||||
let requestHeader = "GET " & uri.path & " HTTP/1.1" & CRLF & $headers
|
||||
await writer.write(requestHeader)
|
||||
let res = await reader.readHeaders()
|
||||
var stream: AsyncStream
|
||||
|
||||
var res: seq[byte]
|
||||
if uri.scheme == "https":
|
||||
let tlsstream = newTLSClientAsyncStream(reader, writer, "", flags = flags)
|
||||
stream = AsyncStream(
|
||||
reader: tlsstream.reader,
|
||||
writer: tlsstream.writer)
|
||||
|
||||
await tlsstream.writer.write(requestHeader)
|
||||
res = await tlsstream.reader.readHeaders()
|
||||
else:
|
||||
stream = AsyncStream(
|
||||
reader: reader,
|
||||
writer: writer)
|
||||
await stream.writer.write(requestHeader)
|
||||
res = await stream.reader.readHeaders()
|
||||
|
||||
if res.len == 0:
|
||||
raise newException(ValueError, "Empty response from server")
|
||||
|
||||
@ -674,14 +695,13 @@ proc initiateHandshake(
|
||||
" Header reason: " & resHeader.reason() &
|
||||
" Address: " & $transp.remoteAddress())
|
||||
|
||||
return AsyncStream(
|
||||
reader: reader,
|
||||
writer: writer)
|
||||
return stream
|
||||
|
||||
proc connect*(
|
||||
_: type WebSocket,
|
||||
uri: Uri,
|
||||
protocols: seq[string] = @[],
|
||||
flags: set[TLSFlags] = {},
|
||||
version = WSDefaultVersion,
|
||||
frameSize = WSDefaultFrameSize,
|
||||
onPing: ControlCb = nil,
|
||||
@ -695,8 +715,10 @@ proc connect*(
|
||||
case uri.scheme
|
||||
of "ws":
|
||||
uri.scheme = "http"
|
||||
of "wss":
|
||||
uri.scheme = "https"
|
||||
else:
|
||||
raise newException(WSWrongUriSchemeError, "uri scheme has to be 'ws'")
|
||||
raise newException(WSWrongUriSchemeError, "uri scheme has to be 'ws' or 'wss'")
|
||||
|
||||
var headerData = [
|
||||
("Connection", "Upgrade"),
|
||||
@ -711,7 +733,7 @@ proc connect*(
|
||||
headers.add("Sec-WebSocket-Protocol", protocols.join(", "))
|
||||
|
||||
let address = initTAddress(uri.hostname & ":" & uri.port)
|
||||
let stream = await initiateHandshake(uri, address, headers)
|
||||
let stream = await initiateHandshake(uri, address, headers, flags)
|
||||
|
||||
# Client data should be masked.
|
||||
return WebSocket(
|
||||
@ -748,6 +770,36 @@ proc connect*(
|
||||
return await WebSocket.connect(
|
||||
parseUri(uri),
|
||||
protocols,
|
||||
{},
|
||||
version,
|
||||
frameSize,
|
||||
onPing,
|
||||
onPong,
|
||||
onClose)
|
||||
|
||||
proc tlsConnect*(
|
||||
_: type WebSocket,
|
||||
host: string,
|
||||
port: Port,
|
||||
path: string,
|
||||
protocols: seq[string] = @[],
|
||||
flags: set[TLSFlags] = {},
|
||||
version = WSDefaultVersion,
|
||||
frameSize = WSDefaultFrameSize,
|
||||
onPing: ControlCb = nil,
|
||||
onPong: ControlCb = nil,
|
||||
onClose: CloseCb = nil): Future[WebSocket] {.async.} =
|
||||
|
||||
var uri = "wss://" & host & ":" & $port
|
||||
if path.startsWith("/"):
|
||||
uri.add path
|
||||
else:
|
||||
uri.add "/" & path
|
||||
|
||||
return await WebSocket.connect(
|
||||
parseUri(uri),
|
||||
protocols,
|
||||
flags,
|
||||
version,
|
||||
frameSize,
|
||||
onPing,
|
Loading…
x
Reference in New Issue
Block a user