import {Inject, Instantiable, Singleton} from '../../di' import {Collection, HTTPStatus} from '../../util' import {HTTPKernelModule} from './HTTPKernelModule' import {Logging} from '../../service/Logging' import {AppClass} from '../../lifecycle/AppClass' import {Request} from '../lifecycle/Request' import {error} from '../response/ErrorResponseFactory' import {HTTPError} from '../HTTPError' /** * Interface for fluently registering kernel modules into the kernel. */ export interface ModuleRegistrationFluency { /** * If no argument is provided, the module will be registered before the core module. * If an argument is provided, the module will be registered before the other specified module. * @param other */ before: (other?: Instantiable) => HTTPKernel, /** * If no argument is provided, the module will be registered after the core module. * If an argument is provided, the module will be registered after the other specified module. * @param other */ after: (other?: Instantiable) => HTTPKernel, /** The module will be registered as the first module in the preflight. */ first: () => HTTPKernel, /** The module will be registered as the last module in the postflight. */ last: () => HTTPKernel, /** The module will be registered as the core handler for the request. */ core: () => HTTPKernel, } /** * A singleton class that handles requests, applying logic in modular layers. */ @Singleton() export class HTTPKernel extends AppClass { @Inject() protected readonly logging!: Logging /** * Collection of preflight modules to apply. * @type Collection */ protected preflight: Collection = new Collection() /** * Module considered to be the main handler. * @type HTTPKernelModule */ protected inflight?: HTTPKernelModule /** * Collection of postflight modules to apply. * @type Collection */ protected postflight: Collection = new Collection() /** * Handle the incoming request, applying the preflight modules, inflight module, then postflight modules. * @param {Request} request * @return Promise */ public async handle(request: Request): Promise { try { for (const module of this.preflight.toArray()) { if ( !request.response.blockingWriteback() || module.executeWithBlockingWriteback ) { this.logging.verbose(`Applying pre-flight HTTP kernel module: ${module.constructor.name}`) request = await module.apply(request) } else { this.logging.verbose(`Skipping pre-flight HTTP kernel module because of blocking write-back: ${module.constructor.name}`) } } if (this.inflight) { if ( !request.response.blockingWriteback() || this.inflight.executeWithBlockingWriteback ) { this.logging.verbose(`Applying core HTTP kernel module: ${this.inflight.constructor.name}`) request = await this.inflight.apply(request) } else { this.logging.verbose(`Skipping core HTTP kernel module because of blocking write-back: ${this.inflight.constructor.name}`) } } for (const module of this.postflight.toArray()) { if ( !request.response.blockingWriteback() || module.executeWithBlockingWriteback ) { this.logging.verbose(`Applying post-flight HTTP kernel module: ${module.constructor.name}`) request = await module.apply(request) } else { this.logging.verbose(`Skipping post-flight HTTP kernel module because of blocking write-back: ${module.constructor.name}`) } } } catch (e: any) { this.logging.error(e) const status = (e instanceof HTTPError && e.status) ? e.status : HTTPStatus.INTERNAL_SERVER_ERROR await error(e).status(status) .write(request) } this.logging.verbose('Finished kernel lifecycle') return request } /** * Get a fluent interface for registering the given kernel module. * @param {Instantiable} module * @return ModuleRegistrationFluency */ public register(module: Instantiable): ModuleRegistrationFluency { this.logging.verbose(`Registering HTTP kernel module: ${module.name}`) return { before: (other?: Instantiable): HTTPKernel => { if ( !other ) { this.preflight = this.preflight.push(this.app().make(module)) return this } let foundIdx = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other) if ( typeof foundIdx !== 'undefined' ) { this.preflight = this.preflight.put(foundIdx, this.app().make(module)) return this } else { foundIdx = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other) } if ( typeof foundIdx !== 'undefined' ) { this.postflight = this.postflight.put(foundIdx, this.app().make(module)) } return this }, after: (other?: Instantiable): HTTPKernel => { if ( !other ) { this.postflight = this.postflight.push(this.app().make(module)) return this } let foundIdx = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other) if ( typeof foundIdx !== 'undefined' ) { this.preflight = this.preflight.put(foundIdx + 1, this.app().make(module)) return this } else { foundIdx = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other) } if ( typeof foundIdx !== 'undefined' ) { this.postflight = this.postflight.put(foundIdx + 1, this.app().make(module)) } return this }, first: (): HTTPKernel => { this.preflight = this.preflight.put(0, this.app().make(module)) return this }, last: (): HTTPKernel => { this.postflight = this.postflight.push(this.app().make(module)) return this }, core: (): HTTPKernel => { this.inflight = this.app().make(module) return this }, } } }