nim-websock/tests/testwebsockets.nim

938 lines
25 KiB
Nim

import std/[strutils, random]
import pkg/[
httputils,
chronos,
chronicles,
stew/byteutils]
import ./asynctest
import ../ws/ws
import ./keys
var server: HttpServer
let
address = initTAddress("127.0.0.1:8888")
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
clientFlags = {NoVerifyHost, NoVerifyServerName}
secureKey = TLSPrivateKey.init(SecureKey)
secureCert = TLSCertificate.init(SecureCert)
const WSPath = when defined secure: "/wss" else: "/ws"
proc rndStr*(size: int): string =
for _ in .. size:
add(result, char(rand(int('A') .. int('z'))))
proc rndBin*(size: int): seq[byte] =
for _ in .. size:
add(result, byte(rand(0 .. 255)))
proc waitForClose(ws: WSSession) {.async.} =
try:
while ws.readystate != ReadyState.Closed:
discard await ws.recv()
except CatchableError:
debug "Closing websocket"
proc createServer(
address = initTAddress("127.0.0.1:8888"),
tlsPrivateKey = secureKey,
tlsCertificate = secureCert,
handler: HttpAsyncCallback = nil,
flags: set[ServerFlags] = socketFlags,
tlsFlags: set[TLSFlags] = {},
tlsMinVersion = TLSVersion.TLS12,
tlsMaxVersion = TLSVersion.TLS12): HttpServer =
when defined secure:
TlsHttpServer.create(
address = address,
tlsPrivateKey = tlsPrivateKey,
tlsCertificate = tlsCertificate,
handler = handler,
flags = flags,
tlsFlags = tlsFlags,
tlsMinVersion = tlsMinVersion,
tlsMaxVersion = tlsMaxVersion)
else:
HttpServer.create(
address = address,
handler = handler,
flags = flags)
proc connectClient*(
address = initTAddress("127.0.0.1:8888"),
path = WSPath,
protocols: seq[string] = @["proto"],
flags: set[TLSFlags] = clientFlags,
version = WSDefaultVersion,
frameSize = WSDefaultFrameSize,
onPing: ControlCb = nil,
onPong: ControlCb = nil,
onClose: CloseCb = nil,
rng: Rng = nil): Future[WSSession] {.async.} =
let secure = when defined secure: true else: false
return await WebSocket.connect(
address = address,
flags = flags,
path = path,
secure = secure,
protocols = protocols,
version = version,
frameSize = frameSize,
onPing = onPing,
onPong = onPong,
onClose = onClose,
rng = rng)
suite "Test handshake":
teardown:
server.stop()
await server.closeWait()
test "Should not select incorrect protocol":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let
server = WSServer.new(protos = ["proto"])
ws = await server.handleRequest(request)
check ws.proto == ""
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
protocols = @["wrongproto"])
check session.proto == ""
await session.stream.closeWait()
test "Test for incorrect version":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["ws"])
expect WSVersionError:
discard await server.handleRequest(request)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
expect WSFailedUpgradeError:
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
version = 14)
test "Test for client headers":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
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")
await request.sendError(Http500)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
expect WSFailedUpgradeError:
discard await connectClient()
test "Test for incorrect scheme":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
expect WSProtoMismatchError:
let ws = await server.handleRequest(request)
check ws.readyState == ReadyState.Closed
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let uri = "wx://127.0.0.1:8888/ws"
expect WSWrongUriSchemeError:
discard await WebSocket.connect(
parseUri(uri),
protocols = @["proto"])
# test "AsyncStream leaks test":
# check:
# getTracker("async.stream.reader").isLeaked() == false
# getTracker("async.stream.writer").isLeaked() == false
# getTracker("stream.server").isLeaked() == false
# getTracker("stream.transport").isLeaked() == false
suite "Test transmission":
teardown:
server.stop()
await server.closeWait()
test "Send text message message with payload of length 65535":
let testString = rndStr(65535)
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
let servRes = await ws.recv()
check string.fromBytes(servRes) == testString
await ws.waitForClose()
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await session.send(testString)
await session.close()
test "Server - test reading simple frame":
let testString = "Hello!"
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
let servRes = await ws.recv()
check string.fromBytes(servRes) == testString
await ws.waitForClose()
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await session.send(testString)
await session.close()
test "Client - test reading simple frame":
let testString = "Hello!"
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
await ws.send(testString)
await ws.close()
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
var clientRes = await session.recv()
check string.fromBytes(clientRes) == testString
await waitForClose(session)
# test "AsyncStream leaks test":
# check:
# getTracker("async.stream.reader").isLeaked() == false
# getTracker("async.stream.writer").isLeaked() == false
# getTracker("stream.server").isLeaked() == false
# getTracker("stream.transport").isLeaked() == false
suite "Test ping-pong":
teardown:
server.stop()
await server.closeWait()
test "Send text Message fragmented into 2 fragments, one ping with payload in-between":
var ping, pong = false
let testString = "1234567890"
let msg = toBytes(testString)
let maxFrameSize = 5
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(
protos = ["proto"],
onPing = proc(data: openArray[byte]) =
ping = true
)
let ws = await server.handleRequest(request)
let respData = await ws.recv()
check string.fromBytes(respData) == testString
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
frameSize = maxFrameSize,
onPong = proc(data: openArray[byte]) =
pong = true
)
let maskKey = genMaskKey(newRng())
await session.stream.writer.write(
(await Frame(
fin: false,
rsv1: false,
rsv2: false,
rsv3: false,
opcode: Opcode.Text,
mask: true,
data: msg[0..4],
maskKey: maskKey)
.encode()))
await session.ping()
await session.stream.writer.write(
(await Frame(
fin: true,
rsv1: false,
rsv2: false,
rsv3: false,
opcode: Opcode.Cont,
mask: true,
data: msg[5..9],
maskKey: maskKey)
.encode()))
await session.close()
check:
ping
pong
test "Server - test ping-pong control messages":
var ping, pong = false
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(
protos = ["proto"],
onPong = proc(data: openArray[byte]) =
pong = true
)
let ws = await server.handleRequest(request)
await ws.ping()
await ws.close()
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onPing = proc(data: openArray[byte]) =
ping = true
)
await waitForClose(session)
check:
ping
pong
test "Client - test ping-pong control messages":
var ping, pong = false
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(
protos = ["proto"],
onPing = proc(data: openArray[byte]) =
ping = true
)
let ws = await server.handleRequest(request)
await waitForClose(ws)
check:
ping
pong
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onPong = proc(data: openArray[byte]) =
pong = true
)
await session.ping()
await session.close()
# test "AsyncStream leaks test":
# check:
# getTracker("async.stream.reader").isLeaked() == false
# getTracker("async.stream.writer").isLeaked() == false
# getTracker("stream.server").isLeaked() == false
# getTracker("stream.transport").isLeaked() == false
suite "Test framing":
teardown:
server.stop()
await server.closeWait()
test "should split message into frames":
let testString = "1234567890"
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
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 waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
frameSize = 5)
await session.send(testString)
await session.close()
test "should fail to read past max message size":
let testString = "1234567890"
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
await ws.send(testString)
await ws.close()
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
expect WSMaxMessageSizeError:
discard await session.recv(5)
await waitForClose(session)
# test "AsyncStream leaks test":
# check:
# getTracker("async.stream.reader").isLeaked() == false
# getTracker("async.stream.writer").isLeaked() == false
# getTracker("stream.server").isLeaked() == false
# getTracker("stream.transport").isLeaked() == false
suite "Test Closing":
teardown:
server.stop()
await server.closeWait()
test "Server closing":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
await ws.close()
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await waitForClose(session)
check session.readyState == ReadyState.Closed
test "Server closing with status":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
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 server = WSServer.new(
protos = ["proto"],
onClose = closeServer
)
let ws = await server.handleRequest(request)
await ws.close()
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
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 session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onClose = clientClose)
await waitForClose(session)
check session.readyState == ReadyState.Closed
test "Client closing":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await session.close()
test "Client closing with status":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
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 server = WSServer.new(
protos = ["proto"],
onClose = closeServer
)
let ws = await server.handleRequest(request)
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
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 session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onClose = clientClose)
await session.close()
check session.readyState == ReadyState.Closed
test "Server closing with valid close code 3999":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
await ws.close(code = Status.ReservedCode)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
proc closeClient(status: Status, reason: string): CloseResult{.gcsafe,
raises: [Defect].} =
try:
check status == Status.ReservedCode
return (Status.ReservedCode, "Reserved Status")
except Exception as exc:
raise newException(Defect, exc.msg)
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onClose = closeClient)
await waitForClose(session)
test "Client closing with valid close code 3999":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
proc closeServer(status: Status, reason: string): CloseResult{.gcsafe,
raises: [Defect].} =
try:
check status == Status.ReservedCode
return (Status.ReservedCode, "Reserved Status")
except Exception as exc:
raise newException(Defect, exc.msg)
let server = WSServer.new(
protos = ["proto"],
onClose = closeServer
)
let ws = await server.handleRequest(request)
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await session.close(code = Status.ReservedCode)
test "Server closing with Payload of length 2":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
# Close with payload of length 2
await ws.close(reason = "HH")
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await waitForClose(session)
test "Client closing with Payload of length 2":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
# Close with payload of length 2
await session.close(reason = "HH")
# test "AsyncStream leaks test":
# check:
# getTracker("async.stream.reader").isLeaked() == false
# getTracker("async.stream.writer").isLeaked() == false
# getTracker("stream.server").isLeaked() == false
# getTracker("stream.transport").isLeaked() == false
suite "Test Payload":
teardown:
server.stop()
await server.closeWait()
test "Test payload message length":
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
expect WSPayloadTooLarge:
discard await ws.recv()
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let str = rndStr(126)
let session = await connectClient()
await session.ping(str.toBytes())
await session.close()
test "Test single empty payload":
let emptyStr = ""
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
let servRes = await ws.recv()
check string.fromBytes(servRes) == emptyStr
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await session.send(emptyStr)
await session.close()
test "Test multiple empty payload":
let emptyStr = ""
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
let servRes = await ws.recv()
check string.fromBytes(servRes) == emptyStr
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
for i in 0..3:
await session.send(emptyStr)
await session.close()
test "Send ping with small text payload":
let testData = toBytes("Hello, world!")
var ping, pong = false
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(
protos = ["proto"],
onPing = proc(data: openArray[byte]) =
ping = data == testData)
let ws = await server.handleRequest(request)
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onPong = proc(data: openArray[byte]) =
pong = true
)
await session.ping(testData)
await session.close()
check:
ping
pong
# test "AsyncStream leaks test":
# check:
# getTracker("async.stream.reader").isLeaked() == false
# getTracker("async.stream.writer").isLeaked() == false
# getTracker("stream.server").isLeaked() == false
# getTracker("stream.transport").isLeaked() == false
suite "Test Binary message with Payload":
teardown:
server.stop()
await server.closeWait()
test "Test binary message with single empty payload message":
let emptyData = newSeq[byte](0)
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
let servRes = await ws.recv()
check:
servRes == emptyData
ws.binary == true
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
await session.send(emptyData, Opcode.Binary)
await session.close()
test "Test binary message with multiple empty payload":
let emptyData = newSeq[byte](0)
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(protos = ["proto"])
let ws = await server.handleRequest(request)
let servRes = await ws.recv()
check:
servRes == emptyData
ws.binary == true
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient()
for i in 0..3:
await session.send(emptyData, Opcode.Binary)
await session.close()
test "Send binary data with small text payload":
let testData = rndBin(10)
debug "testData", testData = testData
var ping, pong = false
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(
protos = ["proto"],
onPing = proc(data: openArray[byte]) =
ping = true
)
let ws = await server.handleRequest(request)
let res = await ws.recv()
check:
res == testData
ws.binary == true
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onPong = proc(data: openArray[byte]) =
pong = true
)
await session.send(testData, Opcode.Binary)
await session.close()
test "Send binary message message with payload of length 125":
let testData = rndBin(125)
var ping, pong = false
proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath
let server = WSServer.new(
protos = ["proto"],
onPing = proc(data: openArray[byte]) =
ping = true
)
let ws = await server.handleRequest(request)
let res = await ws.recv()
check:
res == testData
ws.binary == true
await waitForClose(ws)
server = createServer(
address = address,
handler = handle,
flags = {ReuseAddr})
server.start()
let session = await connectClient(
address = initTAddress("127.0.0.1:8888"),
onPong = proc(data: openArray[byte]) =
pong = true
)
await session.send(testData, Opcode.Binary)
await session.close()
# test "AsyncStream leaks test":
# check:
# getTracker("async.stream.reader").isLeaked() == false
# getTracker("async.stream.writer").isLeaked() == false
# getTracker("stream.server").isLeaked() == false
# getTracker("stream.transport").isLeaked() == false