import {Inject, Instantiable, Singleton} from "@extollo/di" import {Collection, HTTPStatus} from "@extollo/util" import {HTTPKernelModule} from "./HTTPKernelModule"; import {Logging} from "../../service/Logging"; import {AppClass} from "../../lifecycle/AppClass"; import {Request} from "../lifecycle/Request"; import {http} from "../response/HTTPErrorResponseFactory"; /** * Interface for fluently registering kernel modules into the kernel. */ export interface ModuleRegistrationFluency { before: (other?: Instantiable) => HTTPKernel, after: (other?: Instantiable) => HTTPKernel, first: () => HTTPKernel, last: () => HTTPKernel, core: () => HTTPKernel, } /** * Error thrown when a kernel module is requested that does not exist w/in the kernel. * @extends Error */ export class KernelModuleNotFoundError extends Error { constructor(name: string) { super(`The kernel module ${name} is not registered with the kernel.`) } } @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()) { this.logging.verbose(`Applying pre-flight HTTP kernel module: ${module.constructor.name}`) request = await module.apply(request) } if (this.inflight) { this.logging.verbose(`Applying core HTTP kernel module: ${this.inflight.constructor.name}`) request = await this.inflight.apply(request) } for (const module of this.postflight.toArray()) { this.logging.verbose(`Applying post-flight HTTP kernel module: ${module.constructor.name}`) request = await module.apply(request) } } catch (e: any) { this.logging.error(e) await http(HTTPStatus.REQUEST_TIMEOUT).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 found_index = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other) if ( typeof found_index !== 'undefined' ) { this.preflight = this.preflight.put(found_index, this.app().make(module)) return this } else { found_index = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other) } if ( typeof found_index !== 'undefined' ) { this.postflight = this.postflight.put(found_index, this.app().make(module)) } else { throw new KernelModuleNotFoundError(other.name) } return this }, after: (other?: Instantiable): HTTPKernel => { if ( !other ) { this.postflight = this.postflight.push(this.app().make(module)) return this } let found_index = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other) if ( typeof found_index !== 'undefined' ) { this.preflight = this.preflight.put(found_index + 1, this.app().make(module)) return this } else { found_index = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other) } if ( typeof found_index !== 'undefined' ) { this.postflight = this.postflight.put(found_index + 1, this.app().make(module)) } else { throw new KernelModuleNotFoundError(other.name) } 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 }, } } }