# 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) import std/[strutils, strutils, sha1] import unittest2 import ../chronos, ../chronos/apps/http/[httpserver, shttpserver, httpclient] import stew/base10 {.used.} # 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": type TestResponseTuple = tuple[status: int, data: string, count: int] 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 proc testBasicAuthorization(): Future[bool] {.async.} = let session = HttpSessionRef.new({HttpClientFlag.NoVerifyHost}, maxRedirections = 10) let url = parseUri("https://guest:guest@jigsaw.w3.org/HTTP/Basic/") let resp = await session.fetch(url) await session.closeWait() if (resp.status == 200) and ("Your browser made it!" in bytesToString(resp.data)): return true else: echo "RESPONSE STATUS = [", resp.status, "]" echo "RESPONSE = [", bytesToString(resp.data), "]" return false 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 n1 = await test1(keepHa, HttpVersion11, {}, {}) n2 = await test2(keepHa, keepHa, HttpVersion11, {}, {}) n3 = await test1(dropHa, HttpVersion11, {}, {}) n4 = await test2(dropHa, dropHa, HttpVersion11, {}, {}) n5 = await test1(keepHa, HttpVersion11, {HttpClientFlag.NewConnectionAlways}, {}) n6 = await test1(keepHa, HttpVersion11, {}, {HttpClientRequestFlag.DedicatedConnection}) n7 = await test1(keepHa, HttpVersion11, {}, {HttpClientRequestFlag.DedicatedConnection, HttpClientRequestFlag.CloseConnection}) n8 = await test1(keepHa, HttpVersion11, {}, {HttpClientRequestFlag.CloseConnection}) n9 = await test1(keepHa, HttpVersion11, {HttpClientFlag.NewConnectionAlways}, {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 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() let session = HttpSessionRef.new(idleTimeout = 1.seconds, idlePeriod = 200.milliseconds) try: var f1 = test(session, ha) var f2 = test(session, ha) await allFutures(f1, f2) check: f1.finished() f1.done() f2.finished() f2.done() 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 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" test "HTTPS basic authorization test": check waitFor(testBasicAuthorization()) == true test "HTTP client connection management test": let address = initTAddress("127.0.0.1:30080") check waitFor(testConnectionManagement(address)) == true test "HTTP client idle connection test": let address = initTAddress("127.0.0.1:30080") check waitFor(testIdleConnection(address)) == true 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