nim-websock/tests/testwebsockets.nim
Arijit Das 6b76bd8261
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>
2021-04-13 16:05:58 -06:00

459 lines
12 KiB
Nim

import std/strutils,httputils
import pkg/[asynctest,
chronos,
chronos/apps/http/httpserver,
stew/byteutils]
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.} =
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)
server = res.get()
server.start()
expect WSFailedUpgradeError:
discard await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["wrongproto"])
test "Test for incorrect version":
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
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)
server = res.get()
server.start()
expect WSFailedUpgradeError:
discard await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["wrongproto"],
version = 14)
test "Test for client headers":
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
if r.isErr():
return
let request = r.get()
check request.uri.path == "/ws"
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")
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
expect WSFailedUpgradeError:
discard await WebSocket.connect(
"127.0.0.1",
Port(8888),
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.} =
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.close()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
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.} =
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.close()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["proto"])
var clientRes = await wsClient.recv()
await wsClient.close()
check string.fromBytes(clientRes) == testString
suite "Test ping-pong":
teardown:
await server.closeWait()
test "Server - test ping-pong control messages":
var ping, pong = false
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
if r.isErr():
return
let request = r.get()
check request.uri.path == "/ws"
let ws = await createServer(
request,
"proto",
onPong = proc() =
pong = true
)
await ws.ping()
await ws.close()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["proto"],
onPing = proc() =
ping = true
)
discard await wsClient.recv()
check:
ping
pong
test "Client - test ping-pong control messages":
var ping, pong = false
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
if r.isErr():
return
let request = r.get()
check request.uri.path == "/ws"
let ws = await createServer(
request,
"proto",
onPing = proc() =
ping = true
)
discard await ws.recv()
await ws.close()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["proto"],
onPong = proc() =
pong = true
)
await wsClient.ping()
await wsClient.close()
check:
ping
pong
suite "Test framing":
teardown:
await server.closeWait()
test "should split message into frames":
let testString = "1234567890"
proc cb(r: RequestFence): Future[HttpResponseRef]{.async.} =
if r.isErr():
return dumbResponse()
let request = r.get()
check request.uri.path == "/ws"
let ws = await createServer(request, "proto")
let frame1 = await ws.readFrame()
check not isNil(frame1)
var data1 = newSeq[byte](frame1.remainder().int)
let read1 = await ws.stream.reader.readOnce(addr data1[0], data1.len)
check read1 == 5
let frame2 = await ws.readFrame()
check not isNil(frame2)
var data2 = newSeq[byte](frame2.remainder().int)
let read2 = await ws.stream.reader.readOnce(addr data2[0], data2.len)
check read2 == 5
await ws.close()
return dumbResponse()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["proto"],
frameSize = 5)
await wsClient.send(testString)
await wsClient.close()
test "should fail to read past max message size":
let testString = "1234567890"
proc cb(r: RequestFence): Future[HttpResponseRef] {.async, gcsafe.} =
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.close()
return dumbResponse()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["proto"])
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.} =
if r.isErr():
return dumbResponse()
let request = r.get()
check request.uri.path == "/ws"
let ws = await createServer(request, "proto")
await ws.close()
return dumbResponse()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["proto"])
discard await wsClient.recv()
check wsClient.readyState == ReadyState.Closed
test "Server closing with status":
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
if r.isErr():
return dumbResponse()
let request = r.get()
check request.uri.path == "/ws"
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, "")
let ws = await createServer(
request,
"proto",
onClose = closeServer)
await ws.close()
return dumbResponse()
let res = HttpServerRef.new(address, cb)
server = res.get()
server.start()
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",
Port(8888),
path = "/ws",
protocols = @["proto"],
onClose = clientClose)
discard await wsClient.recv()
check wsClient.readyState == ReadyState.Closed
test "Client closing":
proc cb(r: RequestFence): Future[HttpResponseRef] {.async.} =
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)
server = res.get()
server.start()
let wsClient = await WebSocket.connect(
"127.0.0.1",
Port(8888),
path = "/ws",
protocols = @["proto"])
await wsClient.close()
test "Client closing with status":
proc cb(r: RequestFence): Future[HttpResponseRef] {.async, gcsafe.} =
if r.isErr():
return dumbResponse()
let request = r.get()
check request.uri.path == "/ws"
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)
server = res.get()
server.start()
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",
Port(8888),
path = "/ws",
protocols = @["proto"],
onClose = clientClose)
await wsClient.close()
check wsClient.readyState == ReadyState.Closed