import {Inject, Singleton} from "../di" import {HTTPStatus, withTimeout} from "../util" import {Unit} from "../lifecycle/Unit"; import {createServer, IncomingMessage, Server, ServerResponse} from "http"; import {Logging} from "./Logging"; import {Request} from "../http/lifecycle/Request"; import {HTTPKernel} from "../http/kernel/HTTPKernel"; import {PoweredByHeaderInjectionHTTPModule} from "../http/kernel/module/PoweredByHeaderInjectionHTTPModule"; import {SetSessionCookieHTTPModule} from "../http/kernel/module/SetSessionCookieHTTPModule"; import {InjectSessionHTTPModule} from "../http/kernel/module/InjectSessionHTTPModule"; import {PersistSessionHTTPModule} from "../http/kernel/module/PersistSessionHTTPModule"; import {MountActivatedRouteHTTPModule} from "../http/kernel/module/MountActivatedRouteHTTPModule"; import {ExecuteResolvedRouteHandlerHTTPModule} from "../http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule"; import {error} from "../http/response/ErrorResponseFactory"; import {ExecuteResolvedRoutePreflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePreflightHTTPModule"; import {ExecuteResolvedRoutePostflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePostflightHTTPModule"; import {ParseIncomingBodyHTTPModule} from "../http/kernel/module/ParseIncomingBodyHTTPModule"; import {Config} from "./Config"; /** * Application unit that starts the HTTP/S server, creates Request and Response objects * for it, and handles those requests using the HTTPKernel. */ @Singleton() export class HTTPServer extends Unit { @Inject() protected readonly logging!: Logging @Inject() protected readonly config!: Config @Inject() protected readonly kernel!: HTTPKernel /** The underlying native Node.js server. */ protected server?: Server public async up() { const port = this.config.get('server.port', 8000) // TODO register these by config PoweredByHeaderInjectionHTTPModule.register(this.kernel) SetSessionCookieHTTPModule.register(this.kernel) InjectSessionHTTPModule.register(this.kernel) PersistSessionHTTPModule.register(this.kernel) MountActivatedRouteHTTPModule.register(this.kernel) ExecuteResolvedRouteHandlerHTTPModule.register(this.kernel) ExecuteResolvedRoutePreflightHTTPModule.register(this.kernel) ExecuteResolvedRoutePostflightHTTPModule.register(this.kernel) ParseIncomingBodyHTTPModule.register(this.kernel) await new Promise((res, rej) => { this.server = createServer(this.handler) this.server.listen(port, () => { this.logging.success(`Server listening on port ${port}. Press ^C to stop.`) }) process.on('SIGINT', res) }) } public async down() { if ( this.server ) { this.server.close(err => { if ( err ) { this.logging.error(`Error encountered while closing HTTP server: ${err.message}`) this.logging.debug(err) } }) } } public get handler() { const timeout = this.config.get('server.timeout', 10000) return async (request: IncomingMessage, response: ServerResponse) => { const extolloReq = new Request(request, response) withTimeout(timeout, extolloReq.response.sent$.toPromise()) .onTime(req => { this.logging.verbose(`Request lifecycle finished on time. (Path: ${extolloReq.path})`) }) .late(req => { if ( !extolloReq.bypassTimeout ) { this.logging.warn(`Request lifecycle finished late, so an error response was returned! (Path: ${extolloReq.path})`) } }) .timeout(() => { if ( extolloReq.bypassTimeout ) { this.logging.info(`Request lifecycle has timed out, but bypassRequest was set. (Path: ${extolloReq.path})`) return } this.logging.error(`Request lifecycle has timed out. Will send error response instead. (Path: ${extolloReq.path})`) extolloReq.response.setStatus(HTTPStatus.REQUEST_TIMEOUT) extolloReq.response.body = 'Sorry, your request timed out.' extolloReq.response.send() }) .run() .catch(e => this.logging.error(e)) try { await this.kernel.handle(extolloReq) } catch (e) { await error(e).write(extolloReq) } await extolloReq.response.send() } } }