197 lines
6.8 KiB
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, config]
|
|
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 = chronosTransportDefaultBufferSize,
|
|
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 = chronosTransportDefaultBufferSize,
|
|
backlogSize: int = DefaultBacklogSize,
|
|
httpHeadersTimeout = 10.seconds,
|
|
maxHeadersSize: int = 8192,
|
|
maxRequestBodySize: int = 1_048_576,
|
|
dualstack = DualStackType.Auto
|
|
): HttpResult[SecureHttpServerRef] {.
|
|
deprecated: "Callback could raise only CancelledError, annotate with " &
|
|
"{.async: (raises: [CancelledError]).}".} =
|
|
|
|
proc wrap(req: RequestFence): Future[HttpResponseRef] {.
|
|
async: (raises: [CancelledError]).} =
|
|
try:
|
|
await processCallback(req)
|
|
except CancelledError as exc:
|
|
raise exc
|
|
except CatchableError as exc:
|
|
defaultResponse(exc)
|
|
|
|
SecureHttpServerRef.new(
|
|
address = address,
|
|
processCallback = wrap,
|
|
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
|
|
)
|