You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/http/kernel/HTTPKernel.ts

186 lines
7.3 KiB

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<HTTPKernelModule>) => 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<HTTPKernelModule>) => 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,
}
/**
* 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.`)
}
}
/**
* 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<HTTPKernelModule>
*/
protected preflight: Collection<HTTPKernelModule> = new Collection<HTTPKernelModule>()
/**
* Module considered to be the main handler.
* @type HTTPKernelModule
*/
protected inflight?: HTTPKernelModule
/**
* Collection of postflight modules to apply.
* @type Collection<HTTPKernelModule>
*/
protected postflight: Collection<HTTPKernelModule> = new Collection<HTTPKernelModule>()
/**
* Handle the incoming request, applying the preflight modules, inflight module, then postflight modules.
* @param {Request} request
* @return Promise<Request>
*/
public async handle(request: Request): Promise<Request> {
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<HTTPKernelModule>} module
* @return ModuleRegistrationFluency
*/
public register(module: Instantiable<HTTPKernelModule>): ModuleRegistrationFluency {
this.logging.verbose(`Registering HTTP kernel module: ${module.name}`)
return {
before: (other?: Instantiable<HTTPKernelModule>): 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))
} else {
throw new KernelModuleNotFoundError(other.name)
}
return this
},
after: (other?: Instantiable<HTTPKernelModule>): 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))
} 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
},
}
}
}