# # 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 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.} = if conn.state == HttpState.Alive: conn.state = HttpState.Closing var pending: seq[Future[void]] pending.add(conn.writer.closeWait()) pending.add(conn.reader.closeWait()) try: await allFutures(pending) except CancelledError: await allFutures(pending) # After we going to close everything else. pending.setLen(3) pending[0] = conn.mainReader.closeWait() pending[1] = conn.mainWriter.closeWait() pending[2] = conn.transp.closeWait() try: await allFutures(pending) except CancelledError: await allFutures(pending) reset(cast[SecureHttpConnectionRef](conn)[]) untrackCounter(HttpServerSecureConnectionTrackerName) conn.state = HttpState.Closed 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.closeCb = closeSecConnection trackCounter(HttpServerSecureConnectionTrackerName) 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 as exc: await HttpConnectionRef(sconn).closeWait() let msg = "Unable to establish secure connection, reason [" & $exc.msg & "]" raiseHttpCriticalError(msg) except CatchableError as exc: await HttpConnectionRef(sconn).closeWait() let msg = "Unexpected error while trying to establish secure connection, " & "reason [" & $exc.msg & "]" raiseHttpCriticalError(msg) 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 ): HttpResult[SecureHttpServerRef] {.raises: [].} = 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) 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)