Split HTTPS and HTTP servers. (#165)

* Split HTTPS and HTTP servers.

* Fix review commens
This commit is contained in:
Eugene Kabanov 2021-03-17 15:40:40 +02:00 committed by GitHub
parent 0b78606e41
commit c8eefb9382
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 386 additions and 270 deletions

View File

@ -6,5 +6,5 @@
# Licensed under either of # Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2) # Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import apps/http/httpserver import apps/http/httpserver, apps/http/shttpserver
export httpserver export httpserver, shttpserver

View File

@ -9,9 +9,9 @@
import std/[tables, options, uri, strutils] import std/[tables, options, uri, strutils]
import stew/[results, base10], httputils import stew/[results, base10], httputils
import ../../asyncloop, ../../asyncsync import ../../asyncloop, ../../asyncsync
import ../../streams/[asyncstream, boundstream, chunkstream, tlsstream] import ../../streams/[asyncstream, boundstream, chunkstream]
import httptable, httpcommon, multipart import httptable, httpcommon, multipart
export httptable, httpcommon, httputils, multipart, tlsstream, asyncstream, export httptable, httpcommon, httputils, multipart, asyncstream,
uri, tables, options, results uri, tables, options, results
type type
@ -46,6 +46,10 @@ type
HttpProcessCallback* = HttpProcessCallback* =
proc(req: RequestFence): Future[HttpResponseRef] {.gcsafe.} proc(req: RequestFence): Future[HttpResponseRef] {.gcsafe.}
HttpConnectionCallback* =
proc(server: HttpServerRef,
transp: StreamTransport): Future[HttpConnectionRef] {.gcsafe.}
HttpServer* = object of RootObj HttpServer* = object of RootObj
instance*: StreamServer instance*: StreamServer
address*: TransportAddress address*: TransportAddress
@ -56,16 +60,15 @@ type
serverIdent*: string serverIdent*: string
flags*: set[HttpServerFlags] flags*: set[HttpServerFlags]
socketFlags*: set[ServerFlags] socketFlags*: set[ServerFlags]
secureFlags*: set[TLSFlags]
connections*: Table[string, Future[void]] connections*: Table[string, Future[void]]
acceptLoop*: Future[void] acceptLoop*: Future[void]
lifetime*: Future[void] lifetime*: Future[void]
headersTimeout: Duration headersTimeout: Duration
bufferSize: int
maxHeadersSize: int maxHeadersSize: int
maxRequestBodySize: int maxRequestBodySize: int
processCallback: HttpProcessCallback processCallback: HttpProcessCallback
tlsPrivateKey: TLSPrivateKey createConnCallback: HttpConnectionCallback
tlsCertificate: TLSCertificate
HttpServerRef* = ref HttpServer HttpServerRef* = ref HttpServer
@ -103,9 +106,8 @@ type
HttpConnection* = object of RootObj HttpConnection* = object of RootObj
server*: HttpServerRef server*: HttpServerRef
transp: StreamTransport transp: StreamTransport
mainReader: AsyncStreamReader mainReader*: AsyncStreamReader
mainWriter: AsyncStreamWriter mainWriter*: AsyncStreamWriter
tlsStream: TLSAsyncStream
reader*: AsyncStreamReader reader*: AsyncStreamReader
writer*: AsyncStreamWriter writer*: AsyncStreamWriter
buffer: seq[byte] buffer: seq[byte]
@ -119,6 +121,50 @@ proc init(htype: typedesc[HttpProcessError], error: HTTPServerError,
code: HttpCode): HttpProcessError {.raises: [Defect].} = code: HttpCode): HttpProcessError {.raises: [Defect].} =
HttpProcessError(error: error, exc: exc, remote: remote, code: code) 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], proc new*(htype: typedesc[HttpServerRef],
address: TransportAddress, address: TransportAddress,
processCallback: HttpProcessCallback, processCallback: HttpProcessCallback,
@ -126,9 +172,6 @@ proc new*(htype: typedesc[HttpServerRef],
socketFlags: set[ServerFlags] = {ReuseAddr}, socketFlags: set[ServerFlags] = {ReuseAddr},
serverUri = Uri(), serverUri = Uri(),
serverIdent = "", serverIdent = "",
tlsPrivateKey: TLSPrivateKey = nil,
tlsCertificate: TLSCertificate = nil,
secureFlags: set[TLSFlags] = {},
maxConnections: int = -1, maxConnections: int = -1,
bufferSize: int = 4096, bufferSize: int = 4096,
backlogSize: int = 100, backlogSize: int = 100,
@ -136,50 +179,30 @@ proc new*(htype: typedesc[HttpServerRef],
maxHeadersSize: int = 8192, maxHeadersSize: int = 8192,
maxRequestBodySize: int = 1_048_576): HttpResult[HttpServerRef] = maxRequestBodySize: int = 1_048_576): HttpResult[HttpServerRef] =
if HttpServerFlags.Secure in serverFlags: let serverUri =
if isNil(tlsPrivateKey) or isNil(tlsCertificate): if len(serverUri.hostname) > 0:
return err("PrivateKey or Certificate is missing") serverUri
else:
try:
parseUri("http://" & $address & "/")
except TransportAddressError as exc:
return err(exc.msg)
var res = HttpServerRef( let serverInstance =
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 =
try: try:
if len(serverUri.hostname) > 0 and isAbsolute(serverUri): createStreamServer(address, flags = socketFlags, bufferSize = bufferSize,
serverUri backlog = backlogSize)
else: except TransportOsError as exc:
if HttpServerFlags.Secure in serverFlags: return err(exc.msg)
parseUri("https://" & $address & "/") except CatchableError as exc:
else:
parseUri("http://" & $address & "/")
except TransportAddressError as exc:
return err(exc.msg) return err(exc.msg)
try: var res = HttpServerRef()
res.instance = createStreamServer(address, flags = socketFlags, res[].init(address, serverInstance, processCallback, createConnection,
bufferSize = bufferSize, serverUri, serverFlags, socketFlags, serverIdent, maxConnections,
backlog = backlogSize) bufferSize, backLogSize, httpHeadersTimeout, maxHeadersSize,
# if maxConnections > 0: maxRequestBodySize)
# res.semaphore = newAsyncSemaphore(maxConnections) ok(res)
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)
proc getResponse*(req: HttpRequestRef): HttpResponseRef {.raises: [Defect].} = proc getResponse*(req: HttpRequestRef): HttpResponseRef {.raises: [Defect].} =
if req.response.isNone(): if req.response.isNone():
@ -450,47 +473,32 @@ proc getRequest(conn: HttpConnectionRef): Future[HttpRequestRef] {.async.} =
except AsyncStreamLimitError: except AsyncStreamLimitError:
raiseHttpCriticalError("Maximum size of request headers reached", Http431) raiseHttpCriticalError("Maximum size of request headers reached", Http431)
proc new(ht: typedesc[HttpConnectionRef], server: HttpServerRef, proc init*(value: var HttpConnection, server: HttpServerRef,
transp: StreamTransport): HttpConnectionRef = transp: StreamTransport) =
let mainReader = newAsyncStreamReader(transp) value = HttpConnection(
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,
server: server, server: server,
transp: transp,
buffer: newSeq[byte](server.maxHeadersSize), buffer: newSeq[byte](server.maxHeadersSize),
mainReader: mainReader, mainReader: newAsyncStreamReader(transp),
mainWriter: mainWriter, mainWriter: newAsyncStreamWriter(transp)
tlsStream: tlsStream,
reader: reader,
writer: writer
) )
proc closeWait(conn: HttpConnectionRef) {.async.} = proc new(ht: typedesc[HttpConnectionRef], server: HttpServerRef,
if HttpServerFlags.Secure in conn.server.flags: transp: StreamTransport): HttpConnectionRef =
# First we will close TLS streams. var res = HttpConnectionRef()
await allFutures(conn.reader.closeWait(), conn.writer.closeWait()) 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. # After we going to close everything else.
await allFutures(conn.mainReader.closeWait(), conn.mainWriter.closeWait(), await allFutures(conn.mainReader.closeWait(), conn.mainWriter.closeWait(),
conn.transp.closeWait()) conn.transp.closeWait())
@ -505,20 +513,7 @@ proc closeWait(req: HttpRequestRef) {.async.} =
proc createConnection(server: HttpServerRef, proc createConnection(server: HttpServerRef,
transp: StreamTransport): Future[HttpConnectionRef] {. transp: StreamTransport): Future[HttpConnectionRef] {.
async.} = async.} =
var conn = HttpConnectionRef.new(server, transp) return 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")
proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} = proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
var var
@ -527,10 +522,9 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
runLoop = false runLoop = false
try: try:
conn = await createConnection(server, transp) conn = await server.createConnCallback(server, transp)
runLoop = true runLoop = true
except CancelledError: except CancelledError:
# We could be cancelled only when we perform TLS handshake, connection
server.connections.del(transp.getId()) server.connections.del(transp.getId())
await transp.closeWait() await transp.closeWait()
return return

View File

@ -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)

View File

@ -7,5 +7,5 @@
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import testmacro, testsync, testsoon, testtime, testfut, testsignal, import testmacro, testsync, testsoon, testtime, testfut, testsignal,
testaddress, testdatagram, teststream, testserver, testbugs, testnet, testaddress, testdatagram, teststream, testserver, testbugs, testnet,
testasyncstream, testhttpserver testasyncstream, testhttpserver, testshttpserver
import testutils import testutils

View File

@ -6,68 +6,10 @@
# Apache License, version 2.0, (LICENSE-APACHEv2) # Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import std/[strutils, unittest, algorithm, strutils] import std/[strutils, unittest, algorithm, strutils]
import ../chronos, ../chronos/apps import ../chronos, ../chronos/apps/http/httpserver
import stew/base10 import stew/base10
# To create self-signed certificate and key you can use openssl when defined(nimHasUsed): {.used.}
# 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 server testing suite": suite "HTTP server testing suite":
type type
@ -89,34 +31,6 @@ suite "HTTP server testing suite":
if not(isNil(transp)): if not(isNil(transp)):
await closeWait(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, proc testTooBigBodyChunked(address: TransportAddress,
operation: TooBigTest): Future[bool] {.async.} = operation: TooBigTest): Future[bool] {.async.} =
var serverRes = false var serverRes = false
@ -606,82 +520,6 @@ suite "HTTP server testing suite":
check waitFor(testPostMultipart2(initTAddress("127.0.0.1:30080"))) == true 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": test "drop() connections test":
const ClientsCount = 10 const ClientsCount = 10

179
tests/testshttpserver.nim Normal file
View File

@ -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