# 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, unittest, algorithm, strutils] import ../chronos, ../chronos/apps suite "HTTP server testing suite": proc httpClient(address: TransportAddress, data: string): Future[string] {.async.} = var transp: StreamTransport try: transp = await connect(address) if len(data) > 0: let wres {.used.} = await transp.write(data) var rres = await transp.read() var sres = newString(len(rres)) if len(rres) > 0: copyMem(addr sres[0], addr rres[0], len(rres)) return sres except CatchableError: return "EXCEPTION" finally: if not(isNil(transp)): await closeWait(transp) test "Request headers timeout test": proc testTimeout(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): let request = r.get() return await request.respond(Http200, "TEST_OK", HttpTable.init()) else: if r.error().error == HTTPServerError.TimeoutError: serverRes = true return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, socketFlags = socketFlags, httpHeadersTimeout = 100.milliseconds) if res.isErr(): return false let server = res.get() server.start() let data = await httpClient(address, "") await server.stop() await server.close() return serverRes and (data.startsWith("HTTP/1.1 408")) check waitFor(testTimeout(initTAddress("127.0.0.1:30080"))) == true test "Empty headers test": proc testEmpty(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): let request = r.get() return await request.respond(Http200, "TEST_OK", HttpTable.init()) else: if r.error().error == HTTPServerError.CriticalError: serverRes = true return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, socketFlags = socketFlags) if res.isErr(): return false let server = res.get() server.start() let data = await httpClient(address, "\r\n\r\n") await server.stop() await server.close() return serverRes and (data.startsWith("HTTP/1.1 400")) check waitFor(testEmpty(initTAddress("127.0.0.1:30080"))) == true test "Too big headers test": proc testTooBig(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): let request = r.get() return await request.respond(Http200, "TEST_OK", HttpTable.init()) else: if r.error().error == HTTPServerError.CriticalError: serverRes = true return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, maxHeadersSize = 10, socketFlags = socketFlags) if res.isErr(): return false let server = res.get() server.start() let data = await httpClient(address, "GET / HTTP/1.1\r\n\r\n") await server.stop() await server.close() return serverRes and (data.startsWith("HTTP/1.1 413")) check waitFor(testTooBig(initTAddress("127.0.0.1:30080"))) == true test "Query arguments test": proc testQuery(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): let request = r.get() var kres = newSeq[string]() for k, v in request.query.stringItems(): kres.add(k & ":" & v) sort(kres) serverRes = true return await request.respond(Http200, "TEST_OK:" & kres.join(":"), HttpTable.init()) else: serverRes = false return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, socketFlags = socketFlags) if res.isErr(): return false let server = res.get() server.start() let data1 = await httpClient(address, "GET /?a=1&a=2&b=3&c=4 HTTP/1.0\r\n\r\n") let data2 = await httpClient(address, "GET /?a=%D0%9F&%D0%A4=%D0%91&b=%D0%A6&c=%D0%AE HTTP/1.0\r\n\r\n") await server.stop() await server.close() let r = serverRes and (data1.find("TEST_OK:a:1:a:2:b:3:c:4") >= 0) and (data2.find("TEST_OK:a:П:b:Ц:c:Ю:Ф:Б") >= 0) return r check waitFor(testQuery(initTAddress("127.0.0.1:30080"))) == true test "Headers test": proc testHeaders(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): let request = r.get() var kres = newSeq[string]() for k, v in request.headers.stringItems(): kres.add(k & ":" & v) sort(kres) serverRes = true return await request.respond(Http200, "TEST_OK:" & kres.join(":"), HttpTable.init()) else: serverRes = false return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, socketFlags = socketFlags) if res.isErr(): return false let server = res.get() server.start() let message = "GET / HTTP/1.0\r\n" & "Host: www.google.com\r\n" & "Content-Type: text/html\r\n" & "Expect: 100-continue\r\n" & "Cookie: 1\r\n" & "Cookie: 2\r\n\r\n" let expect = "TEST_OK:content-type:text/html:cookie:1:cookie:2" & ":expect:100-continue:host:www.google.com" let data = await httpClient(address, message) await server.stop() await server.close() return serverRes and (data.find(expect) >= 0) check waitFor(testHeaders(initTAddress("127.0.0.1:30080"))) == true test "POST arguments (application/x-www-form-urlencoded) test": proc testPostUrl(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): var kres = newSeq[string]() let request = r.get() if request.meth in PostMethods: let post = await request.post() for k, v in post.stringItems(): kres.add(k & ":" & v) sort(kres) serverRes = true return await request.respond(Http200, "TEST_OK:" & kres.join(":"), HttpTable.init()) else: serverRes = false return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, socketFlags = socketFlags) if res.isErr(): return false let server = res.get() server.start() let message = "POST / HTTP/1.0\r\n" & "Content-Type: application/x-www-form-urlencoded\r\n" & "Content-Length: 20" & "Cookie: 2\r\n\r\n" & "a=a&b=b&c=c&d=%D0%9F" let data = await httpClient(address, message) let expect = "TEST_OK:a:a:b:b:c:c:d:П" await server.stop() await server.close() return serverRes and (data.find(expect) >= 0) check waitFor(testPostUrl(initTAddress("127.0.0.1:30080"))) == true test "POST arguments (multipart/form-data) test": proc testPostMultipart(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): var kres = newSeq[string]() let request = r.get() if request.meth in PostMethods: let post = await request.post() for k, v in post.stringItems(): kres.add(k & ":" & v) sort(kres) serverRes = true return await request.respond(Http200, "TEST_OK:" & kres.join(":"), HttpTable.init()) else: serverRes = false return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, socketFlags = socketFlags) if res.isErr(): return false let server = res.get() server.start() let message = "POST / HTTP/1.0\r\n" & "Host: 127.0.0.1:30080\r\n" & "User-Agent: curl/7.55.1\r\n" & "Accept: */*\r\n" & "Content-Length: 343\r\n" & "Content-Type: multipart/form-data; " & "boundary=------------------------ab5706ba6f80b795\r\n\r\n" & "--------------------------ab5706ba6f80b795\r\n" & "Content-Disposition: form-data; name=\"key1\"\r\n\r\n" & "value1\r\n" & "--------------------------ab5706ba6f80b795\r\n" & "Content-Disposition: form-data; name=\"key2\"\r\n\r\n" & "value2\r\n" & "--------------------------ab5706ba6f80b795\r\n" & "Content-Disposition: form-data; name=\"key2\"\r\n\r\n" & "value4\r\n" & "--------------------------ab5706ba6f80b795--\r\n" let data = await httpClient(address, message) let expect = "TEST_OK:key1:value1:key2:value2:key2:value4" await server.stop() await server.close() return serverRes and (data.find(expect) >= 0) check waitFor(testPostMultipart(initTAddress("127.0.0.1:30080"))) == true test "POST arguments (multipart/form-data + chunked encoding) test": proc testPostMultipart2(address: TransportAddress): Future[bool] {.async.} = var serverRes = false proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {. async.} = if r.isOk(): var kres = newSeq[string]() let request = r.get() if request.meth in PostMethods: let post = await request.post() for k, v in post.stringItems(): kres.add(k & ":" & v) sort(kres) serverRes = true return await request.respond(Http200, "TEST_OK:" & kres.join(":"), HttpTable.init()) else: serverRes = false return dumbResponse() let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let res = HttpServerRef.new(address, process, socketFlags = socketFlags) if res.isErr(): return false let server = res.get() server.start() let message = "POST / HTTP/1.0\r\n" & "Host: 127.0.0.1:30080\r\n" & "Transfer-Encoding: chunked\r\n" & "Content-Type: multipart/form-data; boundary=---" & "---------------------f98f0e32c55fa2ae\r\n\r\n" & "271\r\n" & "--------------------------f98f0e32c55fa2ae\r\n" & "Content-Disposition: form-data; name=\"key1\"\r\n\r\n" & "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" & "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n" & "--------------------------f98f0e32c55fa2ae\r\n" & "Content-Disposition: form-data; name=\"key2\"\r\n\r\n" & "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" & "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\r\n" & "--------------------------f98f0e32c55fa2ae\r\n" & "Content-Disposition: form-data; name=\"key2\"\r\n\r\n" & "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" & "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\r\n" & "--------------------------f98f0e32c55fa2ae--\r\n" & "\r\n0\r\n\r\n" let data = await httpClient(address, message) let expect = "TEST_OK:key1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" & "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" & "AAAAA:key2:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" & "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" & "BBB:key2:CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" & "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" await server.stop() await server.close() return serverRes and (data.find(expect) >= 0) check waitFor(testPostMultipart2(initTAddress("127.0.0.1:30080"))) == true test "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