Http Kernel!
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import AppClass from '../lifecycle/AppClass.ts'
|
||||
|
||||
export default class Controller {
|
||||
export default class Controller extends AppClass {
|
||||
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export class Middleware {
|
||||
import AppClass from '../lifecycle/AppClass.ts'
|
||||
|
||||
export default class Middleware extends AppClass {
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
93
lib/src/http/kernel/Kernel.ts
Normal file
93
lib/src/http/kernel/Kernel.ts
Normal 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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
16
lib/src/http/kernel/Module.ts
Normal file
16
lib/src/http/kernel/Module.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
16
lib/src/http/kernel/module/PrepareRequest.ts
Normal file
16
lib/src/http/kernel/module/PrepareRequest.ts
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
24
lib/src/http/kernel/module/SetDatonHeaders.ts
Normal file
24
lib/src/http/kernel/module/SetDatonHeaders.ts
Normal 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
|
||||
}
|
||||
}
|
||||
28
lib/src/http/kernel/module/SetSessionCookie.ts
Normal file
28
lib/src/http/kernel/module/SetSessionCookie.ts
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
77
lib/src/http/type/RouterDefinition.ts
Normal file
77
lib/src/http/type/RouterDefinition.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user