2021-05-10 07:26:36 +00:00
|
|
|
# Chronos Test Suite
|
|
|
|
# (c) Copyright 2021-Present
|
|
|
|
# Status Research & Development GmbH
|
|
|
|
#
|
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
2023-04-30 17:09:36 +00:00
|
|
|
import std/[strutils, sha1]
|
2021-05-10 07:26:36 +00:00
|
|
|
import unittest2
|
|
|
|
import ../chronos, ../chronos/apps/http/[httpserver, shttpserver, httpclient]
|
|
|
|
import stew/base10
|
|
|
|
|
2023-03-31 05:35:04 +00:00
|
|
|
{.used.}
|
2021-05-10 07:26:36 +00:00
|
|
|
|
|
|
|
# To create self-signed certificate and key you can use openssl
|
|
|
|
# openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes \
|
|
|
|
# -keyout example-com.key.pem -days 3650 -out example-com.cert.pem
|
|
|
|
const HttpsSelfSignedRsaKey = """
|
|
|
|
-----BEGIN PRIVATE KEY-----
|
|
|
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn7tXGLKMIMzOG
|
|
|
|
tVzUixax1/ftlSLcpEAkZMORuiCCnYjtIJhGZdzRFZC8fBlfAJZpLIAOfX2L2f1J
|
|
|
|
ZuwpwDkOIvNqKMBrl5Mvkl5azPT0rtnjuwrcqN5NFtbmZPKFYvbjex2aXGqjl5MW
|
|
|
|
nQIs/ZA++DVEXmaN9oDxcZsvRMDKfrGQf9iLeoVL47Gx9KpqNqD/JLIn4LpieumV
|
|
|
|
yYidm6ukTOqHRvrWm36y6VvKW4TE97THacULmkeahtTf8zDJbbh4EO+gifgwgJ2W
|
|
|
|
BUS0+5hMcWu8111mXmanlOVlcoW8fH8RmPjL1eK1Z3j3SVHEf7oWZtIVW5gGA0jQ
|
|
|
|
nfA4K51RAgMBAAECggEANZ7/R13tWKrwouy6DWuz/WlWUtgx333atUQvZhKmWs5u
|
|
|
|
cDjeJmxUC7b1FhoSB9GqNT7uTLIpKkSaqZthgRtNnIPwcU890Zz+dEwqMJgNByvl
|
|
|
|
it+oYjjRco/+YmaNQaYN6yjelPE5Y678WlYb4b29Fz4t0/zIhj/VgEKkKH2tiXpS
|
|
|
|
TIicoM7pSOscEUfaW3yp5bS5QwNU6/AaF1wws0feBACd19ZkcdPvr52jopbhxlXw
|
|
|
|
h3XTV/vXIJd5zWGp0h/Jbd4xcD4MVo2GjfkeORKY6SjDaNzt8OGtePcKnnbUVu8b
|
|
|
|
2XlDxukhDQXqJ3g0sHz47mhvo4JeIM+FgymRm+3QmQKBgQDTawrEA3Zy9WvucaC7
|
|
|
|
Zah02oE9nuvpF12lZ7WJh7+tZ/1ss+Fm7YspEKaUiEk7nn1CAVFtem4X4YCXTBiC
|
|
|
|
Oqq/o+ipv1yTur0ae6m4pwLm5wcMWBh3H5zjfQTfrClNN8yjWv8u3/sq8KesHPnT
|
|
|
|
R92/sMAptAChPgTzQphWbxFiYwKBgQDLWFaBqXfZYVnTyUvKX8GorS6jGWc6Eh4l
|
|
|
|
lAFA+2EBWDICrUxsDPoZjEXrWCixdqLhyehaI3KEFIx2bcPv6X2c7yx3IG5lA/Gx
|
|
|
|
TZiKlY74c6jOTstkdLW9RJbg1VUHUVZMf/Owt802YmEfUI5S5v7jFmKW6VG+io+K
|
|
|
|
+5KYeHD1uwKBgQDMf53KPA82422jFwYCPjLT1QduM2q97HwIomhWv5gIg63+l4BP
|
|
|
|
rzYMYq6+vZUYthUy41OAMgyLzPQ1ZMXQMi83b7R9fTxvKRIBq9xfYCzObGnE5vHD
|
|
|
|
SDDZWvR75muM5Yxr9nkfPkgVIPMO6Hg+hiVYZf96V0LEtNjU9HWmJYkLQQKBgQCQ
|
|
|
|
ULGUdGHKtXy7AjH3/t3CiKaAupa4cANVSCVbqQy/l4hmvfdu+AbH+vXkgTzgNgKD
|
|
|
|
nHh7AI1Vj//gTSayLlQn/Nbh9PJkXtg5rYiFUn+VdQBo6yMOuIYDPZqXFtCx0Nge
|
|
|
|
kvCwisHpxwiG4PUhgS+Em259DDonsM8PJFx2OYRx4QKBgEQpGhg71Oi9MhPJshN7
|
|
|
|
dYTowaMS5eLTk2264ARaY+hAIV7fgvUa+5bgTVaWL+Cfs33hi4sMRqlEwsmfds2T
|
|
|
|
cnQiJ4cU20Euldfwa5FLnk6LaWdOyzYt/ICBJnKFRwfCUbS4Bu5rtMEM+3t0wxnJ
|
|
|
|
IgaD04WhoL9EX0Qo3DC1+0kG
|
|
|
|
-----END PRIVATE KEY-----
|
|
|
|
"""
|
|
|
|
|
|
|
|
# This SSL certificate will expire 13 October 2030.
|
|
|
|
const HttpsSelfSignedRsaCert = """
|
|
|
|
-----BEGIN CERTIFICATE-----
|
|
|
|
MIIDnzCCAoegAwIBAgIUUdcusjDd3XQi3FPM8urdFG3qI+8wDQYJKoZIhvcNAQEL
|
|
|
|
BQAwXzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
|
|
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEYMBYGA1UEAwwPMTI3LjAuMC4xOjQz
|
|
|
|
ODA4MB4XDTIwMTAxMjIxNDUwMVoXDTMwMTAxMDIxNDUwMVowXzELMAkGA1UEBhMC
|
|
|
|
QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp
|
|
|
|
dHMgUHR5IEx0ZDEYMBYGA1UEAwwPMTI3LjAuMC4xOjQzODA4MIIBIjANBgkqhkiG
|
|
|
|
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+7VxiyjCDMzhrVc1IsWsdf37ZUi3KRAJGTD
|
|
|
|
kboggp2I7SCYRmXc0RWQvHwZXwCWaSyADn19i9n9SWbsKcA5DiLzaijAa5eTL5Je
|
|
|
|
Wsz09K7Z47sK3KjeTRbW5mTyhWL243sdmlxqo5eTFp0CLP2QPvg1RF5mjfaA8XGb
|
|
|
|
L0TAyn6xkH/Yi3qFS+OxsfSqajag/ySyJ+C6YnrplcmInZurpEzqh0b61pt+sulb
|
|
|
|
yluExPe0x2nFC5pHmobU3/MwyW24eBDvoIn4MICdlgVEtPuYTHFrvNddZl5mp5Tl
|
|
|
|
ZXKFvHx/EZj4y9XitWd490lRxH+6FmbSFVuYBgNI0J3wOCudUQIDAQABo1MwUTAd
|
|
|
|
BgNVHQ4EFgQUBKha84woY5WkFxKw7qx1cONg1H8wHwYDVR0jBBgwFoAUBKha84wo
|
|
|
|
Y5WkFxKw7qx1cONg1H8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
|
|
|
|
AQEAHZMYt9Ry+Xj3vTbzpGFQzYQVTJlfJWSN6eWNOivRFQE5io9kOBEe5noa8aLo
|
|
|
|
dLkw6ztxRP2QRJmlhGCO9/HwS17ckrkgZp3EC2LFnzxcBmoZu+owfxOT1KqpO52O
|
|
|
|
IKOl8eVohi1pEicE4dtTJVcpI7VCMovnXUhzx1Ci4Vibns4a6H+BQa19a1JSpifN
|
|
|
|
tO8U5jkjJ8Jprs/VPFhJj2O3di53oDHaYSE5eOrm2ZO14KFHSk9cGcOGmcYkUv8B
|
|
|
|
nV5vnGadH5Lvfxb/BCpuONabeRdOxMt9u9yQ89vNpxFtRdZDCpGKZBCfmUP+5m3m
|
|
|
|
N8r5CwGcIX/XPC3lKazzbZ8baA==
|
|
|
|
-----END CERTIFICATE-----
|
|
|
|
"""
|
|
|
|
|
|
|
|
suite "HTTP client testing suite":
|
|
|
|
|
2022-10-13 10:18:13 +00:00
|
|
|
type
|
|
|
|
TestResponseTuple = tuple[status: int, data: string, count: int]
|
|
|
|
|
2021-05-10 07:26:36 +00:00
|
|
|
proc createBigMessage(message: string, size: int): seq[byte] =
|
|
|
|
var res = newSeq[byte](size)
|
|
|
|
for i in 0 ..< len(res):
|
|
|
|
res[i] = byte(message[i mod len(message)])
|
|
|
|
res
|
|
|
|
|
|
|
|
proc createServer(address: TransportAddress,
|
|
|
|
process: HttpProcessCallback, secure: bool): HttpServerRef =
|
|
|
|
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
|
|
|
if secure:
|
|
|
|
let secureKey = TLSPrivateKey.init(HttpsSelfSignedRsaKey)
|
|
|
|
let secureCert = TLSCertificate.init(HttpsSelfSignedRsaCert)
|
|
|
|
let res = SecureHttpServerRef.new(address, process,
|
|
|
|
socketFlags = socketFlags,
|
|
|
|
tlsPrivateKey = secureKey,
|
|
|
|
tlsCertificate = secureCert)
|
|
|
|
HttpServerRef(res.get())
|
|
|
|
else:
|
|
|
|
let res = HttpServerRef.new(address, process, socketFlags = socketFlags)
|
|
|
|
res.get()
|
|
|
|
|
|
|
|
proc createSession(secure: bool,
|
|
|
|
maxRedirections = HttpMaxRedirections): HttpSessionRef =
|
|
|
|
if secure:
|
|
|
|
HttpSessionRef.new({HttpClientFlag.NoVerifyHost,
|
|
|
|
HttpClientFlag.NoVerifyServerName},
|
|
|
|
maxRedirections = maxRedirections)
|
|
|
|
else:
|
|
|
|
HttpSessionRef.new(maxRedirections = maxRedirections)
|
|
|
|
|
|
|
|
proc testMethods(address: TransportAddress,
|
|
|
|
secure: bool): Future[int] {.async.} =
|
|
|
|
let RequestTests = [
|
|
|
|
(MethodGet, "/test/get"),
|
|
|
|
(MethodPost, "/test/post"),
|
|
|
|
(MethodHead, "/test/head"),
|
|
|
|
(MethodPut, "/test/put"),
|
|
|
|
(MethodDelete, "/test/delete"),
|
|
|
|
(MethodTrace, "/test/trace"),
|
|
|
|
(MethodOptions, "/test/options"),
|
|
|
|
(MethodConnect, "/test/connect"),
|
|
|
|
(MethodPatch, "/test/patch")
|
|
|
|
]
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
|
|
|
async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test/get", "/test/post", "/test/head", "/test/put",
|
|
|
|
"/test/delete", "/test/trace", "/test/options", "/test/connect",
|
|
|
|
"/test/patch", "/test/error":
|
|
|
|
return await request.respond(Http200, request.uri.path)
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
var counter = 0
|
|
|
|
|
|
|
|
var session = createSession(secure)
|
|
|
|
|
|
|
|
for item in RequestTests:
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[1])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[1])
|
|
|
|
var req = HttpClientRequestRef.new(session, ha, item[0])
|
|
|
|
let response = await fetch(req)
|
|
|
|
if response.status == 200:
|
|
|
|
let data = cast[string](response.data)
|
|
|
|
if data == item[1]:
|
|
|
|
inc(counter)
|
|
|
|
await req.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
for item in RequestTests:
|
|
|
|
var session = createSession(secure)
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[1])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[1])
|
|
|
|
var req = HttpClientRequestRef.new(session, ha, item[0])
|
|
|
|
let response = await fetch(req)
|
|
|
|
if response.status == 200:
|
|
|
|
let data = cast[string](response.data)
|
|
|
|
if data == item[1]:
|
|
|
|
inc(counter)
|
|
|
|
await req.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return counter
|
|
|
|
|
|
|
|
proc testResponseStreamReadingTest(address: TransportAddress,
|
|
|
|
secure: bool): Future[int] {.async.} =
|
|
|
|
let ResponseTests = [
|
|
|
|
(MethodGet, "/test/short_size_response", 65600, 1024,
|
|
|
|
"SHORTSIZERESPONSE"),
|
|
|
|
(MethodGet, "/test/long_size_response", 262400, 1024,
|
|
|
|
"LONGSIZERESPONSE"),
|
|
|
|
(MethodGet, "/test/short_chunked_response", 65600, 1024,
|
|
|
|
"SHORTCHUNKRESPONSE"),
|
|
|
|
(MethodGet, "/test/long_chunked_response", 262400, 1024,
|
|
|
|
"LONGCHUNKRESPONSE")
|
|
|
|
]
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
|
|
|
async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test/short_size_response":
|
|
|
|
var response = request.getResponse()
|
|
|
|
var data = createBigMessage(ResponseTests[0][4], ResponseTests[0][2])
|
|
|
|
response.status = Http200
|
|
|
|
await response.sendBody(data)
|
|
|
|
return response
|
|
|
|
of "/test/long_size_response":
|
|
|
|
var response = request.getResponse()
|
|
|
|
var data = createBigMessage(ResponseTests[1][4], ResponseTests[1][2])
|
|
|
|
response.status = Http200
|
|
|
|
await response.sendBody(data)
|
|
|
|
return response
|
|
|
|
of "/test/short_chunked_response":
|
|
|
|
var response = request.getResponse()
|
|
|
|
var data = createBigMessage(ResponseTests[2][4], ResponseTests[2][2])
|
|
|
|
response.status = Http200
|
|
|
|
await response.prepare()
|
|
|
|
var offset = 0
|
|
|
|
while true:
|
|
|
|
if len(data) == offset:
|
|
|
|
break
|
|
|
|
let toWrite = min(1024, len(data) - offset)
|
|
|
|
await response.sendChunk(addr data[offset], toWrite)
|
|
|
|
offset = offset + toWrite
|
|
|
|
await response.finish()
|
|
|
|
return response
|
|
|
|
of "/test/long_chunked_response":
|
|
|
|
var response = request.getResponse()
|
|
|
|
var data = createBigMessage(ResponseTests[3][4], ResponseTests[3][2])
|
|
|
|
response.status = Http200
|
|
|
|
await response.prepare()
|
|
|
|
var offset = 0
|
|
|
|
while true:
|
|
|
|
if len(data) == offset:
|
|
|
|
break
|
|
|
|
let toWrite = min(1024, len(data) - offset)
|
|
|
|
await response.sendChunk(addr data[offset], toWrite)
|
|
|
|
offset = offset + toWrite
|
|
|
|
await response.finish()
|
|
|
|
return response
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
var counter = 0
|
|
|
|
|
|
|
|
var session = createSession(secure)
|
|
|
|
for item in ResponseTests:
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[1])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[1])
|
|
|
|
var req = HttpClientRequestRef.new(session, ha, item[0])
|
|
|
|
var response = await send(req)
|
|
|
|
if response.status == 200:
|
|
|
|
var reader = response.getBodyReader()
|
|
|
|
var res: seq[byte]
|
|
|
|
while true:
|
|
|
|
var data = await reader.read(item[3])
|
|
|
|
res.add(data)
|
|
|
|
if len(data) != item[3]:
|
|
|
|
break
|
|
|
|
await reader.closeWait()
|
|
|
|
if len(res) == item[2]:
|
|
|
|
let expect = createBigMessage(item[4], len(res))
|
|
|
|
if expect == res:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await req.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
for item in ResponseTests:
|
|
|
|
var session = createSession(secure)
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[1])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[1])
|
|
|
|
var req = HttpClientRequestRef.new(session, ha, item[0])
|
|
|
|
var response = await send(req)
|
|
|
|
if response.status == 200:
|
|
|
|
var reader = response.getBodyReader()
|
|
|
|
var res: seq[byte]
|
|
|
|
while true:
|
|
|
|
var data = await reader.read(item[3])
|
|
|
|
res.add(data)
|
|
|
|
if len(data) != item[3]:
|
|
|
|
break
|
|
|
|
await reader.closeWait()
|
|
|
|
if len(res) == item[2]:
|
|
|
|
let expect = createBigMessage(item[4], len(res))
|
|
|
|
if expect == res:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await req.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return counter
|
|
|
|
|
|
|
|
proc testRequestSizeStreamWritingTest(address: TransportAddress,
|
|
|
|
secure: bool): Future[int] {.async.} =
|
|
|
|
let RequestTests = [
|
|
|
|
(MethodPost, "/test/big_request", 65600),
|
|
|
|
(MethodPost, "/test/big_request", 262400)
|
|
|
|
]
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
|
|
|
async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test/big_request":
|
|
|
|
if request.hasBody():
|
|
|
|
let body = await request.getBody()
|
|
|
|
let digest = $secureHash(cast[string](body))
|
|
|
|
return await request.respond(Http200, digest)
|
|
|
|
else:
|
|
|
|
return await request.respond(Http400, "Missing content body")
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
var counter = 0
|
|
|
|
|
|
|
|
var session = createSession(secure)
|
|
|
|
for item in RequestTests:
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[1])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[1])
|
|
|
|
var data = createBigMessage("REQUESTSTREAMMESSAGE", item[2])
|
|
|
|
let headers = [
|
|
|
|
("Content-Type", "application/octet-stream"),
|
|
|
|
("Content-Length", Base10.toString(uint64(len(data))))
|
|
|
|
]
|
|
|
|
var request = HttpClientRequestRef.new(
|
|
|
|
session, ha, item[0], headers = headers
|
|
|
|
)
|
|
|
|
|
|
|
|
var expectDigest = $secureHash(cast[string](data))
|
|
|
|
# Sending big request by 1024bytes long chunks
|
|
|
|
var writer = await open(request)
|
|
|
|
var offset = 0
|
|
|
|
while true:
|
|
|
|
if len(data) == offset:
|
|
|
|
break
|
|
|
|
let toWrite = min(1024, len(data) - offset)
|
|
|
|
await writer.write(addr data[offset], toWrite)
|
|
|
|
offset = offset + toWrite
|
|
|
|
await writer.finish()
|
|
|
|
await writer.closeWait()
|
|
|
|
var response = await request.finish()
|
|
|
|
|
|
|
|
if response.status == 200:
|
|
|
|
var res = await response.getBodyBytes()
|
|
|
|
if cast[string](res) == expectDigest:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await request.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return counter
|
|
|
|
|
|
|
|
proc testRequestChunkedStreamWritingTest(address: TransportAddress,
|
|
|
|
secure: bool): Future[int] {.async.} =
|
|
|
|
let RequestTests = [
|
|
|
|
(MethodPost, "/test/big_chunk_request", 65600),
|
|
|
|
(MethodPost, "/test/big_chunk_request", 262400)
|
|
|
|
]
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
|
|
|
async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test/big_chunk_request":
|
|
|
|
if request.hasBody():
|
|
|
|
let body = await request.getBody()
|
|
|
|
let digest = $secureHash(cast[string](body))
|
|
|
|
return await request.respond(Http200, digest)
|
|
|
|
else:
|
|
|
|
return await request.respond(Http400, "Missing content body")
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
var counter = 0
|
|
|
|
|
|
|
|
var session = createSession(secure)
|
|
|
|
for item in RequestTests:
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[1])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[1])
|
|
|
|
var data = createBigMessage("REQUESTSTREAMMESSAGE", item[2])
|
|
|
|
let headers = [
|
|
|
|
("Content-Type", "application/octet-stream"),
|
|
|
|
("Transfer-Encoding", "chunked")
|
|
|
|
]
|
|
|
|
var request = HttpClientRequestRef.new(
|
|
|
|
session, ha, item[0], headers = headers
|
|
|
|
)
|
|
|
|
|
|
|
|
var expectDigest = $secureHash(cast[string](data))
|
|
|
|
# Sending big request by 1024bytes long chunks
|
|
|
|
var writer = await open(request)
|
|
|
|
var offset = 0
|
|
|
|
while true:
|
|
|
|
if len(data) == offset:
|
|
|
|
break
|
|
|
|
let toWrite = min(1024, len(data) - offset)
|
|
|
|
await writer.write(addr data[offset], toWrite)
|
|
|
|
offset = offset + toWrite
|
|
|
|
await writer.finish()
|
|
|
|
await writer.closeWait()
|
|
|
|
var response = await request.finish()
|
|
|
|
|
|
|
|
if response.status == 200:
|
|
|
|
var res = await response.getBodyBytes()
|
|
|
|
if cast[string](res) == expectDigest:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await request.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return counter
|
|
|
|
|
|
|
|
proc testRequestPostUrlEncodedTest(address: TransportAddress,
|
|
|
|
secure: bool): Future[int] {.async.} =
|
|
|
|
let PostRequests = [
|
|
|
|
("/test/post/urlencoded_size",
|
|
|
|
"field1=value1&field2=value2&field3=value3", "value1:value2:value3"),
|
|
|
|
("/test/post/urlencoded_chunked",
|
|
|
|
"field1=longlonglongvalue1&field2=longlonglongvalue2&" &
|
|
|
|
"field3=longlonglongvalue3", "longlonglongvalue1:longlonglongvalue2:" &
|
|
|
|
"longlonglongvalue3")
|
|
|
|
]
|
|
|
|
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
|
|
|
async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test/post/urlencoded_size", "/test/post/urlencoded_chunked":
|
|
|
|
if request.hasBody():
|
|
|
|
var postTable = await request.post()
|
|
|
|
let body = postTable.getString("field1") & ":" &
|
|
|
|
postTable.getString("field2") & ":" &
|
|
|
|
postTable.getString("field3")
|
|
|
|
return await request.respond(Http200, body)
|
|
|
|
else:
|
|
|
|
return await request.respond(Http400, "Missing content body")
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
var counter = 0
|
|
|
|
|
|
|
|
## Sized url-encoded form
|
|
|
|
block:
|
|
|
|
var session = createSession(secure)
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, PostRequests[0][0])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, PostRequests[0][0])
|
|
|
|
let headers = [
|
|
|
|
("Content-Type", "application/x-www-form-urlencoded"),
|
|
|
|
]
|
|
|
|
var request = HttpClientRequestRef.new(
|
|
|
|
session, ha, MethodPost, headers = headers,
|
|
|
|
body = cast[seq[byte]](PostRequests[0][1]))
|
|
|
|
var response = await send(request)
|
|
|
|
|
|
|
|
if response.status == 200:
|
|
|
|
var res = await response.getBodyBytes()
|
|
|
|
if cast[string](res) == PostRequests[0][2]:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await request.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
## Chunked url-encoded form
|
|
|
|
block:
|
|
|
|
var session = createSession(secure)
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, PostRequests[1][0])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, PostRequests[1][0])
|
|
|
|
let headers = [
|
|
|
|
("Content-Type", "application/x-www-form-urlencoded"),
|
|
|
|
("Transfer-Encoding", "chunked")
|
|
|
|
]
|
|
|
|
var request = HttpClientRequestRef.new(
|
|
|
|
session, ha, MethodPost, headers = headers)
|
|
|
|
|
|
|
|
var data = PostRequests[1][1]
|
|
|
|
|
|
|
|
var writer = await open(request)
|
|
|
|
var offset = 0
|
|
|
|
while true:
|
|
|
|
if len(data) == offset:
|
|
|
|
break
|
|
|
|
let toWrite = min(16, len(data) - offset)
|
|
|
|
await writer.write(addr data[offset], toWrite)
|
|
|
|
offset = offset + toWrite
|
|
|
|
await writer.finish()
|
|
|
|
await writer.closeWait()
|
|
|
|
var response = await request.finish()
|
|
|
|
if response.status == 200:
|
|
|
|
var res = await response.getBodyBytes()
|
|
|
|
if cast[string](res) == PostRequests[1][2]:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await request.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return counter
|
|
|
|
|
|
|
|
proc testRequestPostMultipartTest(address: TransportAddress,
|
|
|
|
secure: bool): Future[int] {.async.} =
|
|
|
|
let PostRequests = [
|
|
|
|
("/test/post/multipart_size", "some-part-boundary",
|
|
|
|
[("field1", "value1"), ("field2", "value2"), ("field3", "value3")],
|
|
|
|
"value1:value2:value3"),
|
|
|
|
("/test/post/multipart_chunked", "some-part-boundary",
|
|
|
|
[("field1", "longlonglongvalue1"), ("field2", "longlonglongvalue2"),
|
|
|
|
("field3", "longlonglongvalue3")],
|
|
|
|
"longlonglongvalue1:longlonglongvalue2:longlonglongvalue3")
|
|
|
|
]
|
|
|
|
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
|
|
|
async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test/post/multipart_size", "/test/post/multipart_chunked":
|
|
|
|
if request.hasBody():
|
|
|
|
var postTable = await request.post()
|
|
|
|
let body = postTable.getString("field1") & ":" &
|
|
|
|
postTable.getString("field2") & ":" &
|
|
|
|
postTable.getString("field3")
|
|
|
|
return await request.respond(Http200, body)
|
|
|
|
else:
|
|
|
|
return await request.respond(Http400, "Missing content body")
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
var counter = 0
|
|
|
|
|
|
|
|
## Sized multipart form
|
|
|
|
block:
|
|
|
|
var mp = MultiPartWriter.init(PostRequests[0][1])
|
|
|
|
mp.begin()
|
|
|
|
for item in PostRequests[0][2]:
|
|
|
|
mp.beginPart(item[0], "", HttpTable.init())
|
|
|
|
mp.write(item[1])
|
|
|
|
mp.finishPart()
|
|
|
|
let data = mp.finish()
|
|
|
|
|
|
|
|
var session = createSession(secure)
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, PostRequests[0][0])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, PostRequests[0][0])
|
|
|
|
let headers = [
|
|
|
|
("Content-Type", "multipart/form-data; boundary=" & PostRequests[0][1]),
|
|
|
|
]
|
|
|
|
var request = HttpClientRequestRef.new(
|
|
|
|
session, ha, MethodPost, headers = headers, body = data)
|
|
|
|
var response = await send(request)
|
|
|
|
if response.status == 200:
|
|
|
|
var res = await response.getBodyBytes()
|
|
|
|
if cast[string](res) == PostRequests[0][3]:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await request.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
## Chunked multipart form
|
|
|
|
block:
|
|
|
|
var session = createSession(secure)
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, PostRequests[0][0])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, PostRequests[0][0])
|
|
|
|
let headers = [
|
|
|
|
("Content-Type", "multipart/form-data; boundary=" & PostRequests[1][1]),
|
|
|
|
("Transfer-Encoding", "chunked")
|
|
|
|
]
|
|
|
|
var request = HttpClientRequestRef.new(
|
|
|
|
session, ha, MethodPost, headers = headers)
|
|
|
|
var writer = await open(request)
|
|
|
|
var mpw = MultiPartWriterRef.new(writer, PostRequests[1][1])
|
|
|
|
await mpw.begin()
|
|
|
|
for item in PostRequests[1][2]:
|
|
|
|
await mpw.beginPart(item[0], "", HttpTable.init())
|
|
|
|
await mpw.write(item[1])
|
|
|
|
await mpw.finishPart()
|
|
|
|
await mpw.finish()
|
|
|
|
await writer.finish()
|
|
|
|
await writer.closeWait()
|
|
|
|
let response = await request.finish()
|
|
|
|
if response.status == 200:
|
|
|
|
var res = await response.getBodyBytes()
|
|
|
|
if cast[string](res) == PostRequests[1][3]:
|
|
|
|
inc(counter)
|
|
|
|
await response.closeWait()
|
|
|
|
await request.closeWait()
|
|
|
|
await session.closeWait()
|
|
|
|
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return counter
|
|
|
|
|
|
|
|
proc testRequestRedirectTest(address: TransportAddress,
|
|
|
|
secure: bool,
|
|
|
|
max: int): Future[string] {.async.} =
|
|
|
|
var session = createSession(secure, maxRedirections = max)
|
|
|
|
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, "/")
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, "/")
|
|
|
|
let lastAddress = ha.getUri().combine(parseUri("/final/5"))
|
|
|
|
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
|
|
|
async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/":
|
|
|
|
return await request.redirect(Http302, "/redirect/1")
|
|
|
|
of "/redirect/1":
|
|
|
|
return await request.redirect(Http302, "/next/redirect/2")
|
|
|
|
of "/next/redirect/2":
|
|
|
|
return await request.redirect(Http302, "redirect/3")
|
|
|
|
of "/next/redirect/redirect/3":
|
|
|
|
return await request.redirect(Http302, "next/redirect/4")
|
|
|
|
of "/next/redirect/redirect/next/redirect/4":
|
|
|
|
return await request.redirect(Http302, lastAddress)
|
|
|
|
of "/final/5":
|
|
|
|
return await request.respond(Http200, "ok-5")
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
if session.maxRedirections >= 5:
|
|
|
|
let (code, data) = await session.fetch(ha.getUri())
|
|
|
|
await session.closeWait()
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return data.bytesToString() & "-" & $code
|
|
|
|
else:
|
|
|
|
let res =
|
|
|
|
try:
|
|
|
|
let (code {.used.}, data {.used.}) = await session.fetch(ha.getUri())
|
|
|
|
false
|
|
|
|
except HttpRedirectError:
|
|
|
|
true
|
|
|
|
except CatchableError:
|
|
|
|
false
|
|
|
|
await session.closeWait()
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
return "redirect-" & $res
|
|
|
|
|
2021-06-28 23:38:08 +00:00
|
|
|
proc testBasicAuthorization(): Future[bool] {.async.} =
|
2022-07-30 20:16:30 +00:00
|
|
|
let session = HttpSessionRef.new({HttpClientFlag.NoVerifyHost},
|
|
|
|
maxRedirections = 10)
|
|
|
|
let url = parseUri("https://guest:guest@jigsaw.w3.org/HTTP/Basic/")
|
2021-06-28 23:38:08 +00:00
|
|
|
let resp = await session.fetch(url)
|
|
|
|
await session.closeWait()
|
|
|
|
if (resp.status == 200) and
|
2022-07-30 20:16:30 +00:00
|
|
|
("Your browser made it!" in bytesToString(resp.data)):
|
2021-06-28 23:38:08 +00:00
|
|
|
return true
|
|
|
|
else:
|
2023-01-23 23:48:31 +00:00
|
|
|
echo "RESPONSE STATUS = [", resp.status, "]"
|
|
|
|
echo "RESPONSE = [", bytesToString(resp.data), "]"
|
2021-06-28 23:38:08 +00:00
|
|
|
return false
|
|
|
|
|
2022-10-13 10:18:13 +00:00
|
|
|
proc testConnectionManagement(address: TransportAddress): Future[bool] {.
|
|
|
|
async.} =
|
|
|
|
let
|
|
|
|
keepHa = getAddress(address, HttpClientScheme.NonSecure, "/keep")
|
|
|
|
dropHa = getAddress(address, HttpClientScheme.NonSecure, "/drop")
|
|
|
|
|
|
|
|
proc test1(
|
|
|
|
a1: HttpAddress,
|
|
|
|
version: HttpVersion,
|
|
|
|
sessionFlags: set[HttpClientFlag],
|
|
|
|
requestFlags: set[HttpClientRequestFlag]
|
|
|
|
): Future[TestResponseTuple] {.async.} =
|
|
|
|
let session = HttpSessionRef.new(flags = sessionFlags)
|
|
|
|
var
|
|
|
|
data: HttpResponseTuple
|
|
|
|
count = -1
|
|
|
|
request = HttpClientRequestRef.new(session, a1, version = version,
|
|
|
|
flags = requestFlags)
|
|
|
|
try:
|
|
|
|
data = await request.fetch()
|
|
|
|
await request.closeWait()
|
|
|
|
count = session.connectionsCount
|
|
|
|
finally:
|
|
|
|
await session.closeWait()
|
|
|
|
return (data.status, data.data.bytesToString(), count)
|
|
|
|
|
|
|
|
proc test2(
|
|
|
|
a1, a2: HttpAddress,
|
|
|
|
version: HttpVersion,
|
|
|
|
sessionFlags: set[HttpClientFlag],
|
|
|
|
requestFlags: set[HttpClientRequestFlag]
|
|
|
|
): Future[seq[TestResponseTuple]] {.async.} =
|
|
|
|
let session = HttpSessionRef.new(flags = sessionFlags)
|
|
|
|
var
|
|
|
|
data1: HttpResponseTuple
|
|
|
|
data2: HttpResponseTuple
|
|
|
|
count: int = -1
|
|
|
|
request1 = HttpClientRequestRef.new(session, a1, version = version,
|
|
|
|
flags = requestFlags)
|
|
|
|
request2 = HttpClientRequestRef.new(session, a2, version = version,
|
|
|
|
flags = requestFlags)
|
|
|
|
try:
|
|
|
|
data1 = await request1.fetch()
|
|
|
|
data2 = await request2.fetch()
|
|
|
|
await request1.closeWait()
|
|
|
|
await request2.closeWait()
|
|
|
|
count = session.connectionsCount
|
|
|
|
finally:
|
|
|
|
await session.closeWait()
|
|
|
|
return @[(data1.status, data1.data.bytesToString(), count),
|
|
|
|
(data2.status, data2.data.bytesToString(), count)]
|
|
|
|
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/keep":
|
|
|
|
let headers = HttpTable.init([("connection", "keep-alive")])
|
|
|
|
return await request.respond(Http200, "ok", headers = headers)
|
|
|
|
of "/drop":
|
|
|
|
let headers = HttpTable.init([("connection", "close")])
|
|
|
|
return await request.respond(Http200, "ok", headers = headers)
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, false)
|
|
|
|
server.start()
|
|
|
|
|
|
|
|
try:
|
|
|
|
let
|
|
|
|
r1 = await test1(keepHa, HttpVersion10, {}, {})
|
|
|
|
r2 = await test1(keepHa, HttpVersion10,
|
|
|
|
{HttpClientFlag.NewConnectionAlways}, {})
|
|
|
|
r3 = await test1(keepHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection})
|
|
|
|
r4 = await test1(keepHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection,
|
|
|
|
HttpClientRequestFlag.CloseConnection})
|
|
|
|
r5 = await test1(dropHa, HttpVersion10, {}, {})
|
|
|
|
r6 = await test1(dropHa, HttpVersion10,
|
|
|
|
{HttpClientFlag.NewConnectionAlways}, {})
|
|
|
|
r7 = await test1(dropHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection})
|
|
|
|
r8 = await test1(dropHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection,
|
|
|
|
HttpClientRequestFlag.CloseConnection})
|
|
|
|
check:
|
|
|
|
r1 == (200, "ok", 0)
|
|
|
|
r2 == (200, "ok", 0)
|
|
|
|
r3 == (200, "ok", 0)
|
|
|
|
r4 == (200, "ok", 0)
|
|
|
|
r5 == (200, "ok", 0)
|
|
|
|
r6 == (200, "ok", 0)
|
|
|
|
r7 == (200, "ok", 0)
|
|
|
|
r8 == (200, "ok", 0)
|
|
|
|
|
|
|
|
let
|
|
|
|
d1 = await test2(keepHa, dropHa, HttpVersion10, {}, {})
|
|
|
|
d2 = await test2(keepHa, dropHa, HttpVersion10,
|
|
|
|
{HttpClientFlag.NewConnectionAlways}, {})
|
|
|
|
d3 = await test2(keepHa, dropHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection})
|
|
|
|
d4 = await test2(keepHa, dropHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection,
|
|
|
|
HttpClientRequestFlag.CloseConnection})
|
|
|
|
d5 = await test2(dropHa, keepHa, HttpVersion10, {}, {})
|
|
|
|
d6 = await test2(dropHa, keepHa, HttpVersion10,
|
|
|
|
{HttpClientFlag.NewConnectionAlways}, {})
|
|
|
|
d7 = await test2(dropHa, keepHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection})
|
|
|
|
d8 = await test2(dropHa, keepHa, HttpVersion10, {},
|
|
|
|
{HttpClientRequestFlag.DedicatedConnection,
|
|
|
|
HttpClientRequestFlag.CloseConnection})
|
|
|
|
check:
|
|
|
|
d1 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
d2 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
d3 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
d4 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
d5 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
d6 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
d7 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
d8 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
|
|
|
|
let
|
2023-05-12 14:00:37 +00:00
|
|
|
n1 = await test1(keepHa, HttpVersion11,
|
|
|
|
{HttpClientFlag.Http11Pipeline}, {})
|
|
|
|
n2 = await test2(keepHa, keepHa, HttpVersion11,
|
|
|
|
{HttpClientFlag.Http11Pipeline}, {})
|
|
|
|
n3 = await test1(dropHa, HttpVersion11,
|
|
|
|
{HttpClientFlag.Http11Pipeline}, {})
|
|
|
|
n4 = await test2(dropHa, dropHa, HttpVersion11,
|
|
|
|
{HttpClientFlag.Http11Pipeline}, {})
|
2022-10-13 10:18:13 +00:00
|
|
|
n5 = await test1(keepHa, HttpVersion11,
|
2023-05-12 14:00:37 +00:00
|
|
|
{HttpClientFlag.NewConnectionAlways,
|
|
|
|
HttpClientFlag.Http11Pipeline}, {})
|
|
|
|
n6 = await test1(keepHa, HttpVersion11,
|
|
|
|
{HttpClientFlag.Http11Pipeline},
|
2022-10-13 10:18:13 +00:00
|
|
|
{HttpClientRequestFlag.DedicatedConnection})
|
2023-05-12 14:00:37 +00:00
|
|
|
n7 = await test1(keepHa, HttpVersion11,
|
|
|
|
{HttpClientFlag.Http11Pipeline},
|
2022-10-13 10:18:13 +00:00
|
|
|
{HttpClientRequestFlag.DedicatedConnection,
|
|
|
|
HttpClientRequestFlag.CloseConnection})
|
2023-05-12 14:00:37 +00:00
|
|
|
n8 = await test1(keepHa, HttpVersion11,
|
|
|
|
{HttpClientFlag.Http11Pipeline},
|
2022-10-13 10:18:13 +00:00
|
|
|
{HttpClientRequestFlag.CloseConnection})
|
|
|
|
n9 = await test1(keepHa, HttpVersion11,
|
2023-05-12 14:00:37 +00:00
|
|
|
{HttpClientFlag.NewConnectionAlways,
|
|
|
|
HttpClientFlag.Http11Pipeline},
|
2022-10-13 10:18:13 +00:00
|
|
|
{HttpClientRequestFlag.CloseConnection})
|
|
|
|
check:
|
|
|
|
n1 == (200, "ok", 1)
|
|
|
|
n2 == @[(200, "ok", 2), (200, "ok", 2)]
|
|
|
|
n3 == (200, "ok", 0)
|
|
|
|
n4 == @[(200, "ok", 0), (200, "ok", 0)]
|
|
|
|
n5 == (200, "ok", 0)
|
|
|
|
n6 == (200, "ok", 1)
|
|
|
|
n7 == (200, "ok", 0)
|
|
|
|
n8 == (200, "ok", 0)
|
|
|
|
n9 == (200, "ok", 0)
|
|
|
|
finally:
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2023-03-21 13:10:35 +00:00
|
|
|
proc testIdleConnection(address: TransportAddress): Future[bool] {.
|
|
|
|
async.} =
|
|
|
|
let
|
|
|
|
ha = getAddress(address, HttpClientScheme.NonSecure, "/test")
|
|
|
|
|
|
|
|
proc test(
|
|
|
|
session: HttpSessionRef,
|
|
|
|
a: HttpAddress
|
|
|
|
): Future[TestResponseTuple] {.async.} =
|
|
|
|
|
|
|
|
var
|
|
|
|
data: HttpResponseTuple
|
|
|
|
request = HttpClientRequestRef.new(session, a, version = HttpVersion11)
|
|
|
|
try:
|
|
|
|
data = await request.fetch()
|
|
|
|
finally:
|
|
|
|
await request.closeWait()
|
|
|
|
return (data.status, data.data.bytesToString(), 0)
|
|
|
|
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test":
|
|
|
|
return await request.respond(Http200, "ok")
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, false)
|
|
|
|
server.start()
|
2023-05-12 14:00:37 +00:00
|
|
|
let session = HttpSessionRef.new({HttpClientFlag.Http11Pipeline},
|
|
|
|
idleTimeout = 1.seconds,
|
2023-03-21 13:10:35 +00:00
|
|
|
idlePeriod = 200.milliseconds)
|
|
|
|
try:
|
|
|
|
var f1 = test(session, ha)
|
|
|
|
var f2 = test(session, ha)
|
|
|
|
await allFutures(f1, f2)
|
|
|
|
check:
|
|
|
|
f1.finished()
|
2023-06-05 11:03:38 +00:00
|
|
|
f1.completed()
|
2023-03-21 13:10:35 +00:00
|
|
|
f2.finished()
|
2023-06-05 11:03:38 +00:00
|
|
|
f2.completed()
|
2023-03-21 13:10:35 +00:00
|
|
|
f1.read() == (200, "ok", 0)
|
|
|
|
f2.read() == (200, "ok", 0)
|
|
|
|
session.connectionsCount == 2
|
|
|
|
|
|
|
|
await sleepAsync(1500.milliseconds)
|
|
|
|
let resp = await test(session, ha)
|
|
|
|
check:
|
|
|
|
resp == (200, "ok", 0)
|
|
|
|
session.connectionsCount == 1
|
|
|
|
finally:
|
|
|
|
await session.closeWait()
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2023-05-12 14:00:37 +00:00
|
|
|
proc testNoPipeline(address: TransportAddress): Future[bool] {.
|
|
|
|
async.} =
|
|
|
|
let
|
|
|
|
ha = getAddress(address, HttpClientScheme.NonSecure, "/test")
|
|
|
|
hb = getAddress(address, HttpClientScheme.NonSecure, "/keep-test")
|
|
|
|
|
|
|
|
proc test(
|
|
|
|
session: HttpSessionRef,
|
|
|
|
a: HttpAddress
|
|
|
|
): Future[TestResponseTuple] {.async.} =
|
|
|
|
|
|
|
|
var
|
|
|
|
data: HttpResponseTuple
|
|
|
|
request = HttpClientRequestRef.new(session, a, version = HttpVersion11)
|
|
|
|
try:
|
|
|
|
data = await request.fetch()
|
|
|
|
finally:
|
|
|
|
await request.closeWait()
|
|
|
|
return (data.status, data.data.bytesToString(), 0)
|
|
|
|
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
case request.uri.path
|
|
|
|
of "/test":
|
|
|
|
return await request.respond(Http200, "ok")
|
|
|
|
of "/keep-test":
|
|
|
|
let headers = HttpTable.init([("Connection", "keep-alive")])
|
|
|
|
return await request.respond(Http200, "not-alive", headers)
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, false)
|
|
|
|
server.start()
|
|
|
|
let session = HttpSessionRef.new(idleTimeout = 100.seconds,
|
|
|
|
idlePeriod = 10.milliseconds)
|
|
|
|
try:
|
|
|
|
var f1 = test(session, ha)
|
|
|
|
var f2 = test(session, ha)
|
|
|
|
await allFutures(f1, f2)
|
|
|
|
check:
|
|
|
|
f1.finished()
|
2023-06-05 11:03:38 +00:00
|
|
|
f1.completed()
|
2023-05-12 14:00:37 +00:00
|
|
|
f2.finished()
|
2023-06-05 11:03:38 +00:00
|
|
|
f2.completed()
|
2023-05-12 14:00:37 +00:00
|
|
|
f1.read() == (200, "ok", 0)
|
|
|
|
f2.read() == (200, "ok", 0)
|
|
|
|
session.connectionsCount == 0
|
|
|
|
|
|
|
|
await sleepAsync(100.milliseconds)
|
|
|
|
block:
|
|
|
|
let resp = await test(session, ha)
|
|
|
|
check:
|
|
|
|
resp == (200, "ok", 0)
|
|
|
|
session.connectionsCount == 0
|
|
|
|
block:
|
|
|
|
let resp = await test(session, hb)
|
|
|
|
check:
|
|
|
|
resp == (200, "not-alive", 0)
|
|
|
|
session.connectionsCount == 0
|
|
|
|
finally:
|
|
|
|
await session.closeWait()
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2023-05-19 14:25:22 +00:00
|
|
|
proc testServerSentEvents(address: TransportAddress,
|
|
|
|
secure: bool): Future[bool] {.async.} =
|
|
|
|
const
|
|
|
|
SingleGoodTests = [
|
|
|
|
("/test/single/1", "a:b\r\nc: d\re:f\n:comment\r\ng:\n h: j \n\n",
|
|
|
|
@[("a", "b"), ("c", "d"), ("e", "f"), ("g", ""), (" h", "j ")]),
|
|
|
|
("/test/single/2", ":comment\r:\nfield1\r\nfield2:\n\n",
|
|
|
|
@[("field1", ""), ("field2", "")]),
|
|
|
|
("/test/single/3", ":c1\r:c2\nfield1:value1", @[("field1", "value1")]),
|
|
|
|
("/test/single/4", ":c1\r:c2\nfield1:", @[("field1", "")]),
|
|
|
|
("/test/single/5", ":c1\r:c2\nfield1", @[("field1", "")]),
|
|
|
|
("/test/single/6", "a", @[("a", "")]),
|
|
|
|
("/test/single/7", "b:", @[("b", "")]),
|
|
|
|
("/test/single/8", "c:d", @[("c", "d")]),
|
|
|
|
("/test/single/9", ":", @[]),
|
|
|
|
("/test/single/10", "", @[]),
|
|
|
|
("/test/single/11", ":c1\n", @[]),
|
|
|
|
("/test/single/12", ":c1\n:c2\n", @[]),
|
|
|
|
("/test/single/13", ":c1\n:c2\n:c3\n", @[]),
|
|
|
|
("/test/single/14", ":c1\n:c2\n:c3\n:c4", @[]),
|
|
|
|
("/test/single/15", "\r\r", @[("", "")]),
|
|
|
|
("/test/single/15", "\n\n", @[("", "")]),
|
|
|
|
("/test/single/17", "\r\n\r\n", @[("", "")]),
|
|
|
|
("/test/single/18", "\r\n", @[("", "")]),
|
|
|
|
("/test/single/19", "\r", @[("", "")]),
|
|
|
|
("/test/single/20", "\n", @[("", "")])
|
|
|
|
]
|
|
|
|
MultipleGoodTests = [
|
|
|
|
("/test/multiple/1", "a:b\nc:d\n\ne:f\rg:h\r\ri:j\r\nk:l\r\n\r\n", 3,
|
|
|
|
@[@[("a", "b"), ("c", "d")], @[("e", "f"), ("g", "h")],
|
|
|
|
@[("i", "j"), ("k", "l")]]),
|
|
|
|
("/test/multiple/2", "a:b\nc:d\n\ne:f\rg:h\r\ri:j\r\nk:l\r\n\r\n\r\n",
|
|
|
|
4, @[@[("a", "b"), ("c", "d")], @[("e", "f"), ("g", "h")],
|
|
|
|
@[("i", "j"), ("k", "l")], @[("", "")]]),
|
|
|
|
]
|
|
|
|
OverflowTests = [
|
|
|
|
("/test/overflow/1", ":verylongcomment", 1, false),
|
|
|
|
("/test/overflow/2", ":verylongcomment\n:anotherone", 1, false),
|
|
|
|
("/test/overflow/3", "aa\n", 1, true),
|
|
|
|
("/test/overflow/4", "a:b\n", 2, true)
|
|
|
|
]
|
|
|
|
|
|
|
|
proc `==`(a: ServerSentEvent, b: tuple[name: string, value: string]): bool =
|
|
|
|
a.name == b.name and a.data == b.value
|
|
|
|
|
|
|
|
proc `==`(a: seq[ServerSentEvent],
|
|
|
|
b: seq[tuple[name: string, value: string]]): bool =
|
|
|
|
if len(a) != len(b):
|
|
|
|
return false
|
|
|
|
for index, value in a.pairs():
|
|
|
|
if value != b[index]:
|
|
|
|
return false
|
|
|
|
true
|
|
|
|
|
|
|
|
proc `==`(a: seq[seq[ServerSentEvent]],
|
|
|
|
b: seq[seq[tuple[name: string, value: string]]]): bool =
|
|
|
|
if len(a) != len(b):
|
|
|
|
return false
|
|
|
|
for index, value in a.pairs():
|
|
|
|
if value != b[index]:
|
|
|
|
return false
|
|
|
|
true
|
|
|
|
|
|
|
|
proc process(r: RequestFence): Future[HttpResponseRef] {.async.} =
|
|
|
|
if r.isOk():
|
|
|
|
let request = r.get()
|
|
|
|
if request.uri.path.startsWith("/test/single/"):
|
|
|
|
let index =
|
|
|
|
block:
|
|
|
|
var res = -1
|
|
|
|
for index, value in SingleGoodTests.pairs():
|
|
|
|
if value[0] == request.uri.path:
|
|
|
|
res = index
|
|
|
|
break
|
|
|
|
res
|
|
|
|
if index < 0:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
var response = request.getResponse()
|
|
|
|
response.status = Http200
|
|
|
|
await response.sendBody(SingleGoodTests[index][1])
|
|
|
|
return response
|
|
|
|
elif request.uri.path.startsWith("/test/multiple/"):
|
|
|
|
let index =
|
|
|
|
block:
|
|
|
|
var res = -1
|
|
|
|
for index, value in MultipleGoodTests.pairs():
|
|
|
|
if value[0] == request.uri.path:
|
|
|
|
res = index
|
|
|
|
break
|
|
|
|
res
|
|
|
|
if index < 0:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
var response = request.getResponse()
|
|
|
|
response.status = Http200
|
|
|
|
await response.sendBody(MultipleGoodTests[index][1])
|
|
|
|
return response
|
|
|
|
elif request.uri.path.startsWith("/test/overflow/"):
|
|
|
|
let index =
|
|
|
|
block:
|
|
|
|
var res = -1
|
|
|
|
for index, value in OverflowTests.pairs():
|
|
|
|
if value[0] == request.uri.path:
|
|
|
|
res = index
|
|
|
|
break
|
|
|
|
res
|
|
|
|
if index < 0:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
var response = request.getResponse()
|
|
|
|
response.status = Http200
|
|
|
|
await response.sendBody(OverflowTests[index][1])
|
|
|
|
return response
|
|
|
|
else:
|
|
|
|
return await request.respond(Http404, "Page not found")
|
|
|
|
else:
|
|
|
|
return dumbResponse()
|
|
|
|
|
|
|
|
var server = createServer(address, process, secure)
|
|
|
|
server.start()
|
|
|
|
|
|
|
|
var session = createSession(secure)
|
|
|
|
|
|
|
|
try:
|
|
|
|
for item in SingleGoodTests:
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[0])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[0])
|
|
|
|
let
|
|
|
|
req = HttpClientRequestRef.new(session, ha, HttpMethod.MethodGet)
|
|
|
|
response = await req.send()
|
|
|
|
events = await response.getServerSentEvents()
|
|
|
|
check events == item[2]
|
|
|
|
await response.closeWait()
|
|
|
|
await req.closeWait()
|
|
|
|
|
|
|
|
for item in MultipleGoodTests:
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[0])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[0])
|
|
|
|
var req = HttpClientRequestRef.new(session, ha, HttpMethod.MethodGet)
|
|
|
|
var response = await send(req)
|
|
|
|
let events =
|
|
|
|
block:
|
|
|
|
var res: seq[seq[ServerSentEvent]]
|
|
|
|
for i in 0 ..< item[2]:
|
|
|
|
let ires = await response.getServerSentEvents()
|
|
|
|
res.add(ires)
|
|
|
|
res
|
|
|
|
check events == item[3]
|
|
|
|
await closeWait(response)
|
|
|
|
await closeWait(req)
|
|
|
|
|
|
|
|
for item in OverflowTests:
|
|
|
|
let ha =
|
|
|
|
if secure:
|
|
|
|
getAddress(address, HttpClientScheme.Secure, item[0])
|
|
|
|
else:
|
|
|
|
getAddress(address, HttpClientScheme.NonSecure, item[0])
|
|
|
|
var req = HttpClientRequestRef.new(session, ha, HttpMethod.MethodGet)
|
|
|
|
var response = await send(req)
|
|
|
|
let error =
|
|
|
|
try:
|
|
|
|
let events {.used.} = await response.getServerSentEvents(item[2])
|
|
|
|
false
|
|
|
|
except HttpReadLimitError:
|
|
|
|
true
|
|
|
|
except CatchableError:
|
|
|
|
false
|
|
|
|
check error == item[3]
|
|
|
|
await closeWait(response)
|
|
|
|
await closeWait(req)
|
|
|
|
|
|
|
|
finally:
|
|
|
|
await closeWait(session)
|
|
|
|
await server.stop()
|
|
|
|
await server.closeWait()
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2021-05-10 07:26:36 +00:00
|
|
|
test "HTTP all request methods test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testMethods(address, false)) == 18
|
|
|
|
|
|
|
|
test "HTTP(S) all request methods test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testMethods(address, true)) == 18
|
|
|
|
|
|
|
|
test "HTTP client response streaming test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testResponseStreamReadingTest(address, false)) == 8
|
|
|
|
|
|
|
|
test "HTTP(S) client response streaming test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testResponseStreamReadingTest(address, true)) == 8
|
|
|
|
|
|
|
|
test "HTTP client (size) request streaming test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestSizeStreamWritingTest(address, false)) == 2
|
|
|
|
|
|
|
|
test "HTTP(S) client (size) request streaming test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestSizeStreamWritingTest(address, true)) == 2
|
|
|
|
|
|
|
|
test "HTTP client (chunked) request streaming test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestChunkedStreamWritingTest(address, false)) == 2
|
|
|
|
|
|
|
|
test "HTTP(S) client (chunked) request streaming test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestChunkedStreamWritingTest(address, true)) == 2
|
|
|
|
|
|
|
|
test "HTTP client (size + chunked) url-encoded POST test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestPostUrlEncodedTest(address, false)) == 2
|
|
|
|
|
|
|
|
test "HTTP(S) client (size + chunked) url-encoded POST test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestPostUrlEncodedTest(address, true)) == 2
|
|
|
|
|
|
|
|
test "HTTP client (size + chunked) multipart POST test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestPostMultipartTest(address, false)) == 2
|
|
|
|
|
|
|
|
test "HTTP(S) client (size + chunked) multipart POST test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestPostMultipartTest(address, true)) == 2
|
|
|
|
|
|
|
|
test "HTTP client redirection test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestRedirectTest(address, false, 5)) == "ok-5-200"
|
|
|
|
|
|
|
|
test "HTTP(S) client redirection test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestRedirectTest(address, true, 5)) == "ok-5-200"
|
|
|
|
|
|
|
|
test "HTTP client maximum redirections test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestRedirectTest(address, false, 4)) == "redirect-true"
|
|
|
|
|
|
|
|
test "HTTP(S) client maximum redirections test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testRequestRedirectTest(address, true, 4)) == "redirect-true"
|
|
|
|
|
2021-06-28 23:38:08 +00:00
|
|
|
test "HTTPS basic authorization test":
|
|
|
|
check waitFor(testBasicAuthorization()) == true
|
|
|
|
|
2022-10-13 10:18:13 +00:00
|
|
|
test "HTTP client connection management test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testConnectionManagement(address)) == true
|
|
|
|
|
2023-03-21 13:10:35 +00:00
|
|
|
test "HTTP client idle connection test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testIdleConnection(address)) == true
|
|
|
|
|
2023-05-12 14:00:37 +00:00
|
|
|
test "HTTP client no-pipeline test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testNoPipeline(address)) == true
|
2023-06-05 11:03:38 +00:00
|
|
|
|
2023-05-19 14:25:22 +00:00
|
|
|
test "HTTP client server-sent events test":
|
|
|
|
let address = initTAddress("127.0.0.1:30080")
|
|
|
|
check waitFor(testServerSentEvents(address, false)) == true
|
2023-05-12 14:00:37 +00:00
|
|
|
|
2021-05-10 07:26:36 +00:00
|
|
|
test "Leaks test":
|
|
|
|
proc getTrackerLeaks(tracker: string): bool =
|
|
|
|
let tracker = getTracker(tracker)
|
|
|
|
if isNil(tracker): false else: tracker.isLeaked()
|
|
|
|
|
|
|
|
check:
|
|
|
|
getTrackerLeaks("http.body.reader") == false
|
|
|
|
getTrackerLeaks("http.body.writer") == false
|
|
|
|
getTrackerLeaks("httpclient.connection") == false
|
|
|
|
getTrackerLeaks("httpclient.request") == false
|
|
|
|
getTrackerLeaks("httpclient.response") == false
|
|
|
|
getTrackerLeaks("async.stream.reader") == false
|
|
|
|
getTrackerLeaks("async.stream.writer") == false
|
|
|
|
getTrackerLeaks("stream.server") == false
|
|
|
|
getTrackerLeaks("stream.transport") == false
|