import {Inject, Singleton} from '../di' import {ErrorWithContext} from '../util' import {Unit} from '../lifecycle/Unit' import {createServer, IncomingMessage, RequestListener, 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' import {InjectRequestEventBusHTTPModule} from '../http/kernel/module/InjectRequestEventBusHTTPModule' import {Routing} from './Routing' import {RequestLocalStorage} from '../http/RequestLocalStorage' import {Bus} from '../support/bus' /** * 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 @Inject() protected readonly routing!: Routing @Inject() protected readonly bus!: Bus @Inject() protected readonly requestLocalStorage!: RequestLocalStorage /** The underlying native Node.js server. */ protected server?: Server public async up(): Promise { 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) InjectRequestEventBusHTTPModule.register(this.kernel) await new Promise(res => { this.server = createServer(this.handler) this.server.listen(port, () => { this.logging.success(`Server listening on port ${port}.`) res() }) }) } public async down(): Promise { 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 getServer(): Server { if ( !this.server ) { throw new ErrorWithContext('Unable to access server: it has not yet been created') } return this.server } public get handler(): RequestListener { return async (request: IncomingMessage, response: ServerResponse) => { const extolloReq = new Request(request, response) await this.requestLocalStorage.run(extolloReq, async () => { this.logging.info(`${extolloReq.method} ${extolloReq.path}`) try { await this.kernel.handle(extolloReq) } catch (e) { if ( e instanceof Error ) { await error(e).write(extolloReq) } await error(new ErrorWithContext('Unknown error occurred.', { e })) } if ( extolloReq.response.canSend() ) { await extolloReq.response.send() } }).finally(() => { this.logging.verbose('Destroying request container...') extolloReq.destroy() }) } } }