nim-chronos/chronos/apps/http/shttpserver.nim

197 lines
6.8 KiB
Nim

#
# 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)
{.push raises: [].}
import httpserver
import ../../asyncloop, ../../asyncsync
import ../../streams/[asyncstream, tlsstream]
export asyncloop, asyncsync, 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 closeSecConnection(conn: HttpConnectionRef) {.async: (raises: []).} =
if conn.state == HttpState.Alive:
conn.state = HttpState.Closing
var pending: seq[Future[void]]
pending.add(conn.writer.closeWait())
pending.add(conn.reader.closeWait())
pending.add(conn.mainReader.closeWait())
pending.add(conn.mainWriter.closeWait())
pending.add(conn.transp.closeWait())
await noCancel(allFutures(pending))
reset(cast[SecureHttpConnectionRef](conn)[])
untrackCounter(HttpServerSecureConnectionTrackerName)
conn.state = HttpState.Closed
proc new(ht: typedesc[SecureHttpConnectionRef], server: SecureHttpServerRef,
transp: StreamTransport): Result[SecureHttpConnectionRef, string] =
var res = SecureHttpConnectionRef()
HttpConnection(res[]).init(HttpServerRef(server), transp)
let tlsStream =
try:
newTLSServerAsyncStream(res.mainReader, res.mainWriter,
server.tlsPrivateKey,
server.tlsCertificate,
minVersion = TLSVersion.TLS12,
flags = server.secureFlags)
except TLSStreamError as exc:
return err(exc.msg)
res.tlsStream = tlsStream
res.reader = AsyncStreamReader(tlsStream.reader)
res.writer = AsyncStreamWriter(tlsStream.writer)
res.closeCb = closeSecConnection
trackCounter(HttpServerSecureConnectionTrackerName)
ok(res)
proc createSecConnection(server: HttpServerRef,
transp: StreamTransport): Future[HttpConnectionRef] {.
async: (raises: [CancelledError, HttpConnectionError]).} =
let
secureServ = cast[SecureHttpServerRef](server)
sconn = SecureHttpConnectionRef.new(secureServ, transp).valueOr:
raiseHttpConnectionError(error)
try:
await handshake(sconn.tlsStream)
HttpConnectionRef(sconn)
except CancelledError as exc:
await HttpConnectionRef(sconn).closeWait()
raise exc
except AsyncStreamError as exc:
await HttpConnectionRef(sconn).closeWait()
let msg = "Unable to establish secure connection, reason: " & $exc.msg
raiseHttpConnectionError(msg)
proc new*(htype: typedesc[SecureHttpServerRef],
address: TransportAddress,
processCallback: HttpProcessCallback2,
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 = DefaultBacklogSize,
httpHeadersTimeout = 10.seconds,
maxHeadersSize: int = 8192,
maxRequestBodySize: int = 1_048_576,
dualstack = DualStackType.Auto
): 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, dualstack = dualstack)
except TransportOsError as exc:
return err(exc.msg)
let res = SecureHttpServerRef(
address: address,
instance: serverInstance,
processCallback: processCallback,
createConnCallback: createSecConnection,
baseUri: serverUri,
serverIdent: serverIdent,
flags: serverFlags + {HttpServerFlags.Secure},
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: initOrderedTable[string, HttpConnectionHolderRef](),
tlsCertificate: tlsCertificate,
tlsPrivateKey: tlsPrivateKey,
secureFlags: secureFlags
)
ok(res)
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 = DefaultBacklogSize,
httpHeadersTimeout = 10.seconds,
maxHeadersSize: int = 8192,
maxRequestBodySize: int = 1_048_576,
dualstack = DualStackType.Auto
): HttpResult[SecureHttpServerRef] {.
deprecated: "raises missing from process callback".} =
proc processCallback2(req: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} =
try:
await processCallback(req)
except CancelledError as exc:
raise exc
except HttpResponseError as exc:
raise exc
except CatchableError as exc:
# Emulate 3.x behavior
raise (ref HttpCriticalError)(msg: exc.msg, code: Http503)
SecureHttpServerRef.new(
address = address,
processCallback = processCallback2,
tlsPrivateKey = tlsPrivateKey,
tlsCertificate = tlsCertificate,
serverFlags = serverFlags,
socketFlags = socketFlags,
serverUri = serverUri,
serverIdent = serverIdent,
secureFlags = secureFlags,
maxConnections = maxConnections,
bufferSize = bufferSize,
backlogSize = backlogSize,
httpHeadersTimeout = httpHeadersTimeout,
maxHeadersSize = maxHeadersSize,
maxRequestBodySize = maxRequestBodySize,
dualstack = dualstack
)