Http Kernel!

This commit is contained in:
garrettmills
2020-07-22 09:38:17 -05:00
parent 60bb9afa29
commit b9f2f844f3
25 changed files with 402 additions and 43 deletions

View File

@@ -1,4 +1,5 @@
import AppClass from '../lifecycle/AppClass.ts'
export default class Controller {
export default class Controller extends AppClass {
}

View File

@@ -2,6 +2,7 @@ import { Injectable } from '../../../di/src/decorator/Injection.ts'
import { getCookies, setCookie, delCookie, ServerRequest } from '../external/http.ts'
import { InMemCache } from '../support/InMemCache.ts'
import { HTTPRequest } from './type/HTTPRequest.ts'
import {logger} from "../service/logging/global.ts";
export interface Cookie {
key: string,
@@ -23,13 +24,18 @@ export class CookieJar {
this._parsed = getCookies(this.request.to_native)
}
public async get_raw(key: string): Promise<string | undefined> {
return this._parsed[key]
}
public async get(key: string): Promise<MaybeCookie> {
// Try the cache
if ( await this._cache.has(key) )
return this._cache.fetch(key)
if ( await this._cache.has(key) ) {
return JSON.parse((await this._cache.fetch(key)) || '""') as Cookie
}
// If the cache missed, try to parse it and load in cache
if ( key in this._parsed ) {
if ( key in this._parsed ) {
let value = this._parsed[key]
try {
value = JSON.parse(atob(this._parsed[key]))
@@ -41,7 +47,7 @@ export class CookieJar {
original_value: this._parsed[key],
}
await this._cache.put(key, cookie)
await this._cache.put(key, JSON.stringify(cookie))
return cookie
}
}
@@ -54,7 +60,7 @@ export class CookieJar {
original_value,
}
await this._cache.put(key, value)
await this._cache.put(key, JSON.stringify(cookie))
setCookie(this.request.response, { name: key, value: original_value })
}

View File

@@ -1,3 +1,5 @@
export class Middleware {
import AppClass from '../lifecycle/AppClass.ts'
export default class Middleware extends AppClass {
}

View File

@@ -1,7 +1,7 @@
import { HTTPResponse } from './type/HTTPResponse.ts'
import { HTTPRequest } from './type/HTTPRequest.ts'
import { ServerRequest } from '../external/http.ts'
import {CookieJar} from "./CookieJar.ts";
import {HTTPResponse} from './type/HTTPResponse.ts'
import {HTTPRequest} from './type/HTTPRequest.ts'
import {ServerRequest} from '../external/http.ts'
import {CookieJar} from './CookieJar.ts'
export class Response implements HTTPResponse {
public status = 200

View File

@@ -0,0 +1,93 @@
import Module from './Module.ts'
import Instantiable from '../../../../di/src/type/Instantiable.ts'
import AppClass from '../../lifecycle/AppClass.ts'
import {Collection} from '../../collection/Collection.ts'
import {Service} from '../../../../di/src/decorator/Service.ts'
import {Request} from '../Request.ts'
export interface ModuleRegistrationFluency {
before: (other?: Instantiable<Module>) => Kernel,
after: (other?: Instantiable<Module>) => Kernel,
first: () => Kernel,
last: () => Kernel,
}
export class KernelModuleNotFoundError extends Error {
constructor(mod_name: string) {
super(`The kernel module ${mod_name} is not registered with the kernel.`)
}
}
@Service()
export default class Kernel extends AppClass {
protected preflight: Collection<Module> = new Collection<Module>()
protected postflight: Collection<Module> = new Collection<Module>()
public async handle(request: Request): Promise<Request> {
for ( const module of this.preflight.toArray() ) {
request = await module.apply(request)
}
for ( const module of this.postflight.toArray() ) {
request = await module.apply(request)
}
return request
}
public register(module: Instantiable<Module>): ModuleRegistrationFluency {
return {
before: (other?: Instantiable<Module>): Kernel => {
if ( !other ) {
this.preflight = this.preflight.push(this.make(module))
return this
}
let found_index = this.preflight.find((mod: Module) => mod instanceof other)
if ( typeof found_index !== 'undefined' ) {
this.preflight = this.preflight.put(found_index, this.make(module))
} else {
found_index = this.postflight.find((mod: Module) => mod instanceof other)
}
if ( typeof found_index !== 'undefined' ) {
this.postflight = this.postflight.put(found_index, this.make(module))
} else {
throw new KernelModuleNotFoundError(other.name)
}
return this
},
after: (other?: Instantiable<Module>): Kernel => {
if ( !other ) {
this.postflight = this.postflight.push(this.make(module))
return this
}
let found_index = this.preflight.find((mod: Module) => mod instanceof other)
if ( typeof found_index !== 'undefined' ) {
this.preflight = this.preflight.put(found_index + 1, this.make(module))
} else {
found_index = this.postflight.find((mod: Module) => mod instanceof other)
}
if ( typeof found_index !== 'undefined' ) {
this.postflight = this.postflight.put(found_index + 1, this.make(module))
} else {
console.log(this.preflight, this.postflight)
throw new KernelModuleNotFoundError(other.name)
}
return this
},
first: (): Kernel => {
this.preflight = this.preflight.put(0, this.make(module))
return this
},
last: (): Kernel => {
this.postflight = this.postflight.push(this.make(module))
return this
},
}
}
}

View File

@@ -0,0 +1,16 @@
import {Request} from '../Request.ts'
import Kernel from './Kernel.ts'
export default class Module {
public async match(request: Request): Promise<boolean> {
return true
}
public async apply(request: Request): Promise<Request> {
return request
}
public static register(kernel: Kernel) {
kernel.register(this).before()
}
}

View File

@@ -0,0 +1,16 @@
import Module from '../Module.ts'
import Kernel from '../Kernel.ts'
import {Request} from '../../Request.ts'
export default class PrepareRequest extends Module {
public static register(kernel: Kernel) {
kernel.register(this).first()
}
public async apply(request: Request): Promise<Request> {
await request.prepare()
return request
}
}

View File

@@ -0,0 +1,24 @@
import Module from '../Module.ts'
import Kernel from '../Kernel.ts'
import {Request} from '../../Request.ts'
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
import Config from '../../../unit/Config.ts'
@Injectable()
export default class SetDatonHeaders extends Module {
public static register(kernel: Kernel) {
kernel.register(this).after()
}
constructor(
protected readonly config: Config,
) {
super()
}
public async apply(request: Request): Promise<Request> {
const text = this.config.get('server.powered_by.text', 'Daton')
request.response.headers.set('X-Powered-By', text)
return request
}
}

View File

@@ -0,0 +1,28 @@
import Module from '../Module.ts'
import Kernel from '../Kernel.ts'
import {Request} from '../../Request.ts'
import PrepareRequest from './PrepareRequest.ts'
import Utility from '../../../service/utility/Utility.ts'
import {Injectable} from '../../../../../di/src/decorator/Injection.ts'
@Injectable()
export default class SetSessionCookie extends Module {
public static register(kernel: Kernel) {
kernel.register(this).after(PrepareRequest)
}
constructor(
protected utility: Utility,
) {
super()
}
public async apply(request: Request): Promise<Request> {
if ( !(await request.cookies.has('daton.session')) ) {
await request.cookies.set('daton.session', `${this.utility.uuid()}-${this.utility.uuid()}`)
}
return request
}
}

View File

@@ -0,0 +1,77 @@
import {logger} from '../../service/logging/global.ts'
export type RouteVerb = 'get' | 'post' | 'patch' | 'delete' | 'head' | 'put' | 'connect' | 'options' | 'trace'
export type RouteVerbGroup = { [key: string]: string | string[] }
export interface RouterDefinition {
prefix?: string,
middleware?: string[],
get?: RouteVerbGroup,
post?: RouteVerbGroup,
patch?: RouteVerbGroup,
delete?: RouteVerbGroup,
head?: RouteVerbGroup,
put?: RouteVerbGroup,
connect?: RouteVerbGroup,
options?: RouteVerbGroup,
trace?: RouteVerbGroup,
}
export function isRouteVerb(something: any): something is RouteVerb {
const route_verbs = ['get', 'post', 'patch', 'delete', 'head', 'put', 'connect', 'options', 'trace']
return route_verbs.includes(something)
}
export function isRouteVerbGroup(something: any): something is RouteVerbGroup {
if ( !(typeof something === 'object' ) ) return false
for ( const key in something ) {
if ( !something.hasOwnProperty(key) ) continue
if ( typeof key !== 'string' ) {
logger.debug(`Route verb group key is not a string: ${key}`)
return false
}
if (
!(typeof something[key] === 'string')
&& !(Array.isArray(something[key]) && something[key].every((x: any) => typeof x === 'string'))
) {
logger.info(`Route verb group for key ${key} is not a string or array of strings.`)
return false
}
}
return true
}
export function isRouterDefinition(something: any): something is RouterDefinition {
if ( !(typeof something === 'object') ) {
logger.debug('Routing definition is not an object.')
return false
}
for ( const key in something ) {
if (!something.hasOwnProperty(key)) continue
if ( key === 'prefix' ) {
if ( typeof something[key] !== 'string' ) {
logger.debug(`Invalid route prefix: ${something[key]}`)
return false
}
}
else if ( key === 'middleware' ) {
if ( !Array.isArray(something[key]) ) {
logger.debug('Middleware is not an array.')
return false
}
else if ( !something[key].every((x: any) => typeof x === 'string') ) {
logger.debug('Middleware array contains non-string values.')
return false
}
} else if ( isRouteVerb(key) ) {
if ( !isRouteVerbGroup(something[key as any]) ) {
logger.debug('Verb group value is not a valid route verb group.')
return false
}
} else {
logger.debug(`Invalid key: ${key}`)
return false
}
}
return true
}