Continue fleshing out HTTP request classes

This commit is contained in:
2021-03-06 20:58:48 -06:00
parent cd387af2c6
commit e298319bf5
7 changed files with 430 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
import {Request} from "../lifecycle/Request";
import {infer} from "@extollo/util";
/**
* Base type representing a parsed cookie.
*/
export interface HTTPCookie {
key: string,
originalValue: string,
value: any,
exists: boolean,
}
export type MaybeHTTPCookie = HTTPCookie | undefined;
export class HTTPCookieJar {
protected parsed: {[key: string]: HTTPCookie} = {}
constructor(
protected request: Request,
) {
this.parseCookies()
}
get(name: string): MaybeHTTPCookie {
if ( name in this.parsed ) {
return this.parsed[name]
}
}
private parseCookies() {
const cookies = String(this.request.getHeader('cookie'))
cookies.split(';').forEach(cookie => {
const parts = cookie.split('=')
const key = parts.shift()?.trim()
if ( !key ) return;
const value = decodeURI(parts.join('='))
this.parsed[key] = {
key,
originalValue: value,
value: infer(value),
exists: true,
}
})
}
}

View File

@@ -0,0 +1,150 @@
import {Instantiable, Singleton, Inject} from "@extollo/di"
import {Collection} from "@extollo/util"
import {HTTPKernelModule} from "./HTTPKernelModule";
import {Logging} from "../../service/Logging";
import {AppClass} from "../../lifecycle/AppClass";
import {Request} from "../lifecycle/Request";
/**
* Interface for fluently registering kernel modules into the kernel.
*/
export interface ModuleRegistrationFluency {
before: (other?: Instantiable<HTTPKernelModule>) => HTTPKernel,
after: (other?: Instantiable<HTTPKernelModule>) => 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<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()) {
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)
// FIXME handle error response
// const error_response = error(e)
// await error_response.write(request)
}
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 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<HTTPKernelModule>): 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
},
}
}
}

View File

@@ -0,0 +1,33 @@
import {Injectable} from "@extollo/di";
import {AppClass} from "../../lifecycle/AppClass";
import {HTTPKernel} from "./HTTPKernel";
import {Request} from "../lifecycle/Request";
@Injectable()
export class HTTPKernelModule extends AppClass {
/**
* Returns true if the given module should be applied to the incoming request.
* @param {Request} request
* @return Promise<boolean>
*/
public async match(request: Request): Promise<boolean> {
return true
}
/**
* Apply the module to the incoming request.
* @param {Request} request
* @return Promise<Request>
*/
public async apply(request: Request): Promise<Request> {
return request
}
/**
* Register this module with the given HTTP kernel.
* @param {HTTPKernel} kernel
*/
public static register(kernel: HTTPKernel) {
kernel.register(this).before()
}
}

View File

@@ -0,0 +1,14 @@
import {HTTPKernelModule} from "../HTTPKernelModule";
import {Request} from "../../lifecycle/Request";
import {HTTPKernel} from "../HTTPKernel";
export class PrepareRequestHTTPModule extends HTTPKernelModule {
public static register(kernel: HTTPKernel) {
kernel.register(this).first()
}
public async apply(request: Request) {
await request.prepare()
return request
}
}