From c8eefb9382a786993fc703386b0bd446ecf9c037 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Wed, 17 Mar 2021 15:40:40 +0200 Subject: [PATCH] Split HTTPS and HTTP servers. (#165) * Split HTTPS and HTTP servers. * Fix review commens --- chronos/apps.nim | 4 +- chronos/apps/http/httpserver.nim | 200 +++++++++++++++--------------- chronos/apps/http/shttpserver.nim | 105 ++++++++++++++++ tests/testall.nim | 2 +- tests/testhttpserver.nim | 166 +------------------------ tests/testshttpserver.nim | 179 ++++++++++++++++++++++++++ 6 files changed, 386 insertions(+), 270 deletions(-) create mode 100644 chronos/apps/http/shttpserver.nim create mode 100644 tests/testshttpserver.nim diff --git a/chronos/apps.nim b/chronos/apps.nim index 53b3fac..eef6160 100644 --- a/chronos/apps.nim +++ b/chronos/apps.nim @@ -6,5 +6,5 @@ # Licensed under either of # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) -import apps/http/httpserver -export httpserver +import apps/http/httpserver, apps/http/shttpserver +export httpserver, shttpserver diff --git a/chronos/apps/http/httpserver.nim b/chronos/apps/http/httpserver.nim index 20f8ca0..0ab1c11 100644 --- a/chronos/apps/http/httpserver.nim +++ b/chronos/apps/http/httpserver.nim @@ -9,9 +9,9 @@ import std/[tables, options, uri, strutils] import stew/[results, base10], httputils import ../../asyncloop, ../../asyncsync -import ../../streams/[asyncstream, boundstream, chunkstream, tlsstream] +import ../../streams/[asyncstream, boundstream, chunkstream] import httptable, httpcommon, multipart -export httptable, httpcommon, httputils, multipart, tlsstream, asyncstream, +export httptable, httpcommon, httputils, multipart, asyncstream, uri, tables, options, results type @@ -46,6 +46,10 @@ type HttpProcessCallback* = proc(req: RequestFence): Future[HttpResponseRef] {.gcsafe.} + HttpConnectionCallback* = + proc(server: HttpServerRef, + transp: StreamTransport): Future[HttpConnectionRef] {.gcsafe.} + HttpServer* = object of RootObj instance*: StreamServer address*: TransportAddress @@ -56,16 +60,15 @@ type serverIdent*: string flags*: set[HttpServerFlags] socketFlags*: set[ServerFlags] - secureFlags*: set[TLSFlags] connections*: Table[string, Future[void]] acceptLoop*: Future[void] lifetime*: Future[void] headersTimeout: Duration + bufferSize: int maxHeadersSize: int maxRequestBodySize: int processCallback: HttpProcessCallback - tlsPrivateKey: TLSPrivateKey - tlsCertificate: TLSCertificate + createConnCallback: HttpConnectionCallback HttpServerRef* = ref HttpServer @@ -103,9 +106,8 @@ type HttpConnection* = object of RootObj server*: HttpServerRef transp: StreamTransport - mainReader: AsyncStreamReader - mainWriter: AsyncStreamWriter - tlsStream: TLSAsyncStream + mainReader*: AsyncStreamReader + mainWriter*: AsyncStreamWriter reader*: AsyncStreamReader writer*: AsyncStreamWriter buffer: seq[byte] @@ -119,6 +121,50 @@ proc init(htype: typedesc[HttpProcessError], error: HTTPServerError, code: HttpCode): HttpProcessError {.raises: [Defect].} = HttpProcessError(error: error, exc: exc, remote: remote, code: code) +proc init*(value: var HttpServer, + address: TransportAddress, + server: StreamServer, + processCallback: HttpProcessCallback, + createConnCallback: HttpConnectionCallback, + serverUri: Uri, + serverFlags: set[HttpServerFlags] = {}, + socketFlags: set[ServerFlags] = {ReuseAddr}, + serverIdent = "", + maxConnections: int = -1, + bufferSize: int = 4096, + backlogSize: int = 100, + httpHeadersTimeout = 10.seconds, + maxHeadersSize: int = 8192, + maxRequestBodySize: int = 1_048_576) = + + value = HttpServer( + address: address, + instance: server, + processCallback: processCallback, + createConnCallback: createConnCallback, + baseUri: serverUri, + serverIdent: serverIdent, + flags: serverFlags, + socketFlags: socketFlags, + maxConnections: maxConnections, + bufferSize: bufferSize, + backlogSize: backlogSize, + headersTimeout: httpHeadersTimeout, + maxHeadersSize: maxHeadersSize, + maxRequestBodySize: maxRequestBodySize, + # semaphore: + # if maxConnections > 0: + # newAsyncSemaphore(maxConnections) + # else: + # nil + lifetime: newFuture[void]("http.server.lifetime"), + connections: initTable[string, Future[void]]() + ) + +proc createConnection(server: HttpServerRef, + transp: StreamTransport): Future[HttpConnectionRef] {. + gcsafe.} + proc new*(htype: typedesc[HttpServerRef], address: TransportAddress, processCallback: HttpProcessCallback, @@ -126,9 +172,6 @@ proc new*(htype: typedesc[HttpServerRef], socketFlags: set[ServerFlags] = {ReuseAddr}, serverUri = Uri(), serverIdent = "", - tlsPrivateKey: TLSPrivateKey = nil, - tlsCertificate: TLSCertificate = nil, - secureFlags: set[TLSFlags] = {}, maxConnections: int = -1, bufferSize: int = 4096, backlogSize: int = 100, @@ -136,50 +179,30 @@ proc new*(htype: typedesc[HttpServerRef], maxHeadersSize: int = 8192, maxRequestBodySize: int = 1_048_576): HttpResult[HttpServerRef] = - if HttpServerFlags.Secure in serverFlags: - if isNil(tlsPrivateKey) or isNil(tlsCertificate): - return err("PrivateKey or Certificate is missing") + let serverUri = + if len(serverUri.hostname) > 0: + serverUri + else: + try: + parseUri("http://" & $address & "/") + except TransportAddressError as exc: + return err(exc.msg) - var res = HttpServerRef( - address: address, - serverIdent: serverIdent, - maxConnections: maxConnections, - headersTimeout: httpHeadersTimeout, - maxHeadersSize: maxHeadersSize, - maxRequestBodySize: maxRequestBodySize, - processCallback: processCallback, - backLogSize: backLogSize, - flags: serverFlags, - socketFlags: socketFlags, - tlsPrivateKey: tlsPrivateKey, - tlsCertificate: tlsCertificate - ) - - res.baseUri = + let serverInstance = try: - if len(serverUri.hostname) > 0 and isAbsolute(serverUri): - serverUri - else: - if HttpServerFlags.Secure in serverFlags: - parseUri("https://" & $address & "/") - else: - parseUri("http://" & $address & "/") - except TransportAddressError as exc: + createStreamServer(address, flags = socketFlags, bufferSize = bufferSize, + backlog = backlogSize) + except TransportOsError as exc: + return err(exc.msg) + except CatchableError as exc: return err(exc.msg) - try: - res.instance = createStreamServer(address, flags = socketFlags, - bufferSize = bufferSize, - backlog = backlogSize) - # if maxConnections > 0: - # res.semaphore = newAsyncSemaphore(maxConnections) - res.lifetime = newFuture[void]("http.server.lifetime") - res.connections = initTable[string, Future[void]]() - return ok(res) - except TransportOsError as exc: - return err(exc.msg) - except CatchableError as exc: - return err(exc.msg) + var res = HttpServerRef() + res[].init(address, serverInstance, processCallback, createConnection, + serverUri, serverFlags, socketFlags, serverIdent, maxConnections, + bufferSize, backLogSize, httpHeadersTimeout, maxHeadersSize, + maxRequestBodySize) + ok(res) proc getResponse*(req: HttpRequestRef): HttpResponseRef {.raises: [Defect].} = if req.response.isNone(): @@ -450,47 +473,32 @@ proc getRequest(conn: HttpConnectionRef): Future[HttpRequestRef] {.async.} = except AsyncStreamLimitError: raiseHttpCriticalError("Maximum size of request headers reached", Http431) -proc new(ht: typedesc[HttpConnectionRef], server: HttpServerRef, - transp: StreamTransport): HttpConnectionRef = - let mainReader = newAsyncStreamReader(transp) - let mainWriter = newAsyncStreamWriter(transp) - let tlsStream = - if HttpServerFlags.Secure in server.flags: - newTLSServerAsyncStream(mainReader, mainWriter, server.tlsPrivateKey, - server.tlsCertificate, - minVersion = TLSVersion.TLS12, - flags = server.secureFlags) - else: - nil - - let reader = - if isNil(tlsStream): - mainReader - else: - cast[AsyncStreamReader](tlsStream.reader) - - let writer = - if isNil(tlsStream): - mainWriter - else: - cast[AsyncStreamWriter](tlsStream.writer) - - HttpConnectionRef( - transp: transp, +proc init*(value: var HttpConnection, server: HttpServerRef, + transp: StreamTransport) = + value = HttpConnection( server: server, + transp: transp, buffer: newSeq[byte](server.maxHeadersSize), - mainReader: mainReader, - mainWriter: mainWriter, - tlsStream: tlsStream, - reader: reader, - writer: writer + mainReader: newAsyncStreamReader(transp), + mainWriter: newAsyncStreamWriter(transp) ) -proc closeWait(conn: HttpConnectionRef) {.async.} = - if HttpServerFlags.Secure in conn.server.flags: - # First we will close TLS streams. - await allFutures(conn.reader.closeWait(), conn.writer.closeWait()) +proc new(ht: typedesc[HttpConnectionRef], server: HttpServerRef, + transp: StreamTransport): HttpConnectionRef = + var res = HttpConnectionRef() + res[].init(server, transp) + res.reader = res.mainReader + res.writer = res.mainWriter + res +proc closeWait*(conn: HttpConnectionRef) {.async.} = + var pending: seq[Future[void]] + if conn.reader != conn.mainReader: + pending.add(conn.reader.closeWait()) + if conn.writer != conn.mainWriter: + pending.add(conn.writer.closeWait()) + if len(pending) > 0: + await allFutures(pending) # After we going to close everything else. await allFutures(conn.mainReader.closeWait(), conn.mainWriter.closeWait(), conn.transp.closeWait()) @@ -505,20 +513,7 @@ proc closeWait(req: HttpRequestRef) {.async.} = proc createConnection(server: HttpServerRef, transp: StreamTransport): Future[HttpConnectionRef] {. async.} = - var conn = HttpConnectionRef.new(server, transp) - if HttpServerFlags.Secure notin server.flags: - # Non secure connection - return conn - - try: - await handshake(conn.tlsStream) - return conn - except CancelledError as exc: - await conn.closeWait() - raise exc - except TLSStreamError: - await conn.closeWait() - raiseHttpCriticalError("Unable to establish secure connection") + return HttpConnectionRef.new(server, transp) proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} = var @@ -527,10 +522,9 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} = runLoop = false try: - conn = await createConnection(server, transp) + conn = await server.createConnCallback(server, transp) runLoop = true except CancelledError: - # We could be cancelled only when we perform TLS handshake, connection server.connections.del(transp.getId()) await transp.closeWait() return diff --git a/chronos/apps/http/shttpserver.nim b/chronos/apps/http/shttpserver.nim new file mode 100644 index 0000000..ca22ccc --- /dev/null +++ b/chronos/apps/http/shttpserver.nim @@ -0,0 +1,105 @@ +# +# Chronos HTTP/S server implementation +# (c) Copyright 2021-Present +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) +import httpserver +import ../../asyncloop, ../../asyncsync +import ../../streams/[asyncstream, tlsstream] +export httpserver, asyncstream, tlsstream + +type + SecureHttpServer* = object of HttpServer + secureFlags*: set[TLSFlags] + tlsPrivateKey: TLSPrivateKey + tlsCertificate: TLSCertificate + + SecureHttpServerRef* = ref SecureHttpServer + + SecureHttpConnection* = object of HttpConnection + tlsStream*: TLSAsyncStream + + SecureHttpConnectionRef* = ref SecureHttpConnection + +proc new*(ht: typedesc[SecureHttpConnectionRef], server: SecureHttpServerRef, + transp: StreamTransport): SecureHttpConnectionRef = + var res = SecureHttpConnectionRef() + HttpConnection(res[]).init(HttpServerRef(server), transp) + let tlsStream = + newTLSServerAsyncStream(res.mainReader, res.mainWriter, + server.tlsPrivateKey, + server.tlsCertificate, + minVersion = TLSVersion.TLS12, + flags = server.secureFlags) + res.tlsStream = tlsStream + res.reader = AsyncStreamReader(tlsStream.reader) + res.writer = AsyncStreamWriter(tlsStream.writer) + res + +proc createSecConnection(server: HttpServerRef, + transp: StreamTransport): Future[HttpConnectionRef] {. + async.} = + let secureServ = cast[SecureHttpServerRef](server) + var sconn = SecureHttpConnectionRef.new(secureServ, transp) + try: + await handshake(sconn.tlsStream) + return HttpConnectionRef(sconn) + except CancelledError as exc: + await HttpConnectionRef(sconn).closeWait() + raise exc + except TLSStreamError: + await HttpConnectionRef(sconn).closeWait() + raiseHttpCriticalError("Unable to establish secure connection") + +proc new*(htype: typedesc[SecureHttpServerRef], + address: TransportAddress, + processCallback: HttpProcessCallback, + tlsPrivateKey: TLSPrivateKey, + tlsCertificate: TLSCertificate, + serverFlags: set[HttpServerFlags] = {}, + socketFlags: set[ServerFlags] = {ReuseAddr}, + serverUri = Uri(), + serverIdent = "", + secureFlags: set[TLSFlags] = {}, + maxConnections: int = -1, + bufferSize: int = 4096, + backlogSize: int = 100, + httpHeadersTimeout = 10.seconds, + maxHeadersSize: int = 8192, + maxRequestBodySize: int = 1_048_576 + ): HttpResult[SecureHttpServerRef] = + + doAssert(not(isNil(tlsPrivateKey)), "TLS private key must not be nil!") + doAssert(not(isNil(tlsCertificate)), "TLS certificate must not be nil!") + + let serverUri = + if len(serverUri.hostname) > 0: + serverUri + else: + try: + parseUri("https://" & $address & "/") + except TransportAddressError as exc: + return err(exc.msg) + + let serverInstance = + try: + createStreamServer(address, flags = socketFlags, bufferSize = bufferSize, + backlog = backlogSize) + except TransportOsError as exc: + return err(exc.msg) + except CatchableError as exc: + return err(exc.msg) + + var res = SecureHttpServerRef() + HttpServer(res[]).init(address, serverInstance, processCallback, + createSecConnection, serverUri, serverFlags, + socketFlags, serverIdent, maxConnections, + bufferSize, backLogSize, httpHeadersTimeout, + maxHeadersSize, maxRequestBodySize) + res.tlsCertificate = tlsCertificate + res.tlsPrivateKey = tlsPrivateKey + res.secureFlags = secureFlags + ok(res) diff --git a/tests/testall.nim b/tests/testall.nim index 59a66c0..decec74 100644 --- a/tests/testall.nim +++ b/tests/testall.nim @@ -7,5 +7,5 @@ # MIT license (LICENSE-MIT) import testmacro, testsync, testsoon, testtime, testfut, testsignal, testaddress, testdatagram, teststream, testserver, testbugs, testnet, - testasyncstream, testhttpserver + testasyncstream, testhttpserver, testshttpserver import testutils diff --git a/tests/testhttpserver.nim b/tests/testhttpserver.nim index a57b174..5cd20d8 100644 --- a/tests/testhttpserver.nim +++ b/tests/testhttpserver.nim @@ -6,68 +6,10 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) import std/[strutils, unittest, algorithm, strutils] -import ../chronos, ../chronos/apps +import ../chronos, ../chronos/apps/http/httpserver import stew/base10 -# 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----- -""" +when defined(nimHasUsed): {.used.} suite "HTTP server testing suite": type @@ -89,34 +31,6 @@ suite "HTTP server testing suite": if not(isNil(transp)): await closeWait(transp) - proc httpsClient(address: TransportAddress, - data: string, flags = {NoVerifyHost, NoVerifyServerName} - ): Future[string] {.async.} = - var - transp: StreamTransport - tlsstream: TlsAsyncStream - reader: AsyncStreamReader - writer: AsyncStreamWriter - - try: - transp = await connect(address) - reader = newAsyncStreamReader(transp) - writer = newAsyncStreamWriter(transp) - tlsstream = newTLSClientAsyncStream(reader, writer, "", flags = flags) - if len(data) > 0: - await tlsstream.writer.write(data) - var rres = await tlsstream.reader.read() - return bytesToString(rres) - except CatchableError: - return "EXCEPTION" - finally: - if not(isNil(tlsstream)): - await allFutures(tlsstream.reader.closeWait(), - tlsstream.writer.closeWait()) - if not(isNil(reader)): - await allFutures(reader.closeWait(), writer.closeWait(), - transp.closeWait()) - proc testTooBigBodyChunked(address: TransportAddress, operation: TooBigTest): Future[bool] {.async.} = var serverRes = false @@ -606,82 +520,6 @@ suite "HTTP server testing suite": check waitFor(testPostMultipart2(initTAddress("127.0.0.1:30080"))) == true - test "HTTPS server (successful handshake) test": - proc testHTTPS(address: TransportAddress): Future[bool] {.async.} = - var serverRes = false - proc process(r: RequestFence): Future[HttpResponseRef] {. - async.} = - if r.isOk(): - let request = r.get() - serverRes = true - return await request.respond(Http200, "TEST_OK:" & $request.meth, - HttpTable.init()) - else: - serverRes = false - return dumbResponse() - - let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} - let serverFlags = {Secure} - let secureKey = TLSPrivateKey.init(HttpsSelfSignedRsaKey) - let secureCert = TLSCertificate.init(HttpsSelfSignedRsaCert) - let res = HttpServerRef.new(address, process, - socketFlags = socketFlags, - serverFlags = serverFlags, - tlsPrivateKey = secureKey, - tlsCertificate = secureCert) - if res.isErr(): - return false - - let server = res.get() - server.start() - let message = "GET / HTTP/1.0\r\nHost: https://127.0.0.1:80\r\n\r\n" - let data = await httpsClient(address, message) - - await server.stop() - await server.closeWait() - return serverRes and (data.find("TEST_OK:GET") >= 0) - - check waitFor(testHTTPS(initTAddress("127.0.0.1:30080"))) == true - - test "HTTPS server (failed handshake) test": - proc testHTTPS2(address: TransportAddress): Future[bool] {.async.} = - var serverRes = false - var testFut = newFuture[void]() - proc process(r: RequestFence): Future[HttpResponseRef] {. - async.} = - if r.isOk(): - let request = r.get() - serverRes = false - return await request.respond(Http200, "TEST_OK:" & $request.meth, - HttpTable.init()) - else: - serverRes = true - testFut.complete() - return dumbResponse() - - let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} - let serverFlags = {Secure} - let secureKey = TLSPrivateKey.init(HttpsSelfSignedRsaKey) - let secureCert = TLSCertificate.init(HttpsSelfSignedRsaCert) - let res = HttpServerRef.new(address, process, - socketFlags = socketFlags, - serverFlags = serverFlags, - tlsPrivateKey = secureKey, - tlsCertificate = secureCert) - if res.isErr(): - return false - - let server = res.get() - server.start() - let message = "GET / HTTP/1.0\r\nHost: https://127.0.0.1:80\r\n\r\n" - let data = await httpsClient(address, message, {NoVerifyServerName}) - await testFut - await server.stop() - await server.closeWait() - return serverRes and data == "EXCEPTION" - - check waitFor(testHTTPS2(initTAddress("127.0.0.1:30080"))) == true - test "drop() connections test": const ClientsCount = 10 diff --git a/tests/testshttpserver.nim b/tests/testshttpserver.nim new file mode 100644 index 0000000..aedc19a --- /dev/null +++ b/tests/testshttpserver.nim @@ -0,0 +1,179 @@ +# 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, strutils] +import ../chronos, ../chronos/apps/http/shttpserver +import stew/base10 + +when defined(nimHasUsed): {.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 "Secure HTTP server testing suite": + + proc httpsClient(address: TransportAddress, + data: string, flags = {NoVerifyHost, NoVerifyServerName} + ): Future[string] {.async.} = + var + transp: StreamTransport + tlsstream: TlsAsyncStream + reader: AsyncStreamReader + writer: AsyncStreamWriter + + try: + transp = await connect(address) + reader = newAsyncStreamReader(transp) + writer = newAsyncStreamWriter(transp) + tlsstream = newTLSClientAsyncStream(reader, writer, "", flags = flags) + if len(data) > 0: + await tlsstream.writer.write(data) + var rres = await tlsstream.reader.read() + return bytesToString(rres) + except CatchableError: + return "EXCEPTION" + finally: + if not(isNil(tlsstream)): + await allFutures(tlsstream.reader.closeWait(), + tlsstream.writer.closeWait()) + if not(isNil(reader)): + await allFutures(reader.closeWait(), writer.closeWait(), + transp.closeWait()) + + test "HTTPS server (successful handshake) test": + proc testHTTPS(address: TransportAddress): Future[bool] {.async.} = + var serverRes = false + proc process(r: RequestFence): Future[HttpResponseRef] {. + async.} = + if r.isOk(): + let request = r.get() + serverRes = true + return await request.respond(Http200, "TEST_OK:" & $request.meth, + HttpTable.init()) + else: + serverRes = false + return dumbResponse() + + let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} + let serverFlags = {Secure} + let secureKey = TLSPrivateKey.init(HttpsSelfSignedRsaKey) + let secureCert = TLSCertificate.init(HttpsSelfSignedRsaCert) + let res = SecureHttpServerRef.new(address, process, + socketFlags = socketFlags, + serverFlags = serverFlags, + tlsPrivateKey = secureKey, + tlsCertificate = secureCert) + if res.isErr(): + return false + + let server = res.get() + server.start() + let message = "GET / HTTP/1.0\r\nHost: https://127.0.0.1:80\r\n\r\n" + let data = await httpsClient(address, message) + + await server.stop() + await server.closeWait() + return serverRes and (data.find("TEST_OK:GET") >= 0) + + check waitFor(testHTTPS(initTAddress("127.0.0.1:30080"))) == true + + test "HTTPS server (failed handshake) test": + proc testHTTPS2(address: TransportAddress): Future[bool] {.async.} = + var serverRes = false + var testFut = newFuture[void]() + proc process(r: RequestFence): Future[HttpResponseRef] {. + async.} = + if r.isOk(): + let request = r.get() + serverRes = false + return await request.respond(Http200, "TEST_OK:" & $request.meth, + HttpTable.init()) + else: + serverRes = true + testFut.complete() + return dumbResponse() + + let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} + let serverFlags = {Secure} + let secureKey = TLSPrivateKey.init(HttpsSelfSignedRsaKey) + let secureCert = TLSCertificate.init(HttpsSelfSignedRsaCert) + let res = SecureHttpServerRef.new(address, process, + socketFlags = socketFlags, + serverFlags = serverFlags, + tlsPrivateKey = secureKey, + tlsCertificate = secureCert) + if res.isErr(): + return false + + let server = res.get() + server.start() + let message = "GET / HTTP/1.0\r\nHost: https://127.0.0.1:80\r\n\r\n" + let data = await httpsClient(address, message, {NoVerifyServerName}) + await testFut + await server.stop() + await server.closeWait() + return serverRes and data == "EXCEPTION" + + check waitFor(testHTTPS2(initTAddress("127.0.0.1:30080"))) == true