Add support for middleware
This commit is contained in:
parent
2868ca1910
commit
4f23ac7156
@ -58,18 +58,30 @@ export class HTTPKernel extends AppClass {
|
|||||||
public async handle(request: Request): Promise<Request> {
|
public async handle(request: Request): Promise<Request> {
|
||||||
try {
|
try {
|
||||||
for (const module of this.preflight.toArray()) {
|
for (const module of this.preflight.toArray()) {
|
||||||
this.logging.verbose(`Applying pre-flight HTTP kernel module: ${module.constructor.name}`)
|
if ( !request.response.blockingWriteback() || module.executeWithBlockingWriteback ) {
|
||||||
request = await module.apply(request)
|
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 (this.inflight) {
|
||||||
this.logging.verbose(`Applying core HTTP kernel module: ${this.inflight.constructor.name}`)
|
if ( !request.response.blockingWriteback() || this.inflight.executeWithBlockingWriteback ) {
|
||||||
request = await this.inflight.apply(request)
|
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()) {
|
for (const module of this.postflight.toArray()) {
|
||||||
this.logging.verbose(`Applying post-flight HTTP kernel module: ${module.constructor.name}`)
|
if ( !request.response.blockingWriteback() || module.executeWithBlockingWriteback ) {
|
||||||
request = await module.apply(request)
|
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) {
|
} catch (e: any) {
|
||||||
this.logging.error(e)
|
this.logging.error(e)
|
||||||
|
@ -5,6 +5,8 @@ import {Request} from "../lifecycle/Request";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HTTPKernelModule extends AppClass {
|
export class HTTPKernelModule extends AppClass {
|
||||||
|
public readonly executeWithBlockingWriteback: boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given module should be applied to the incoming request.
|
* Returns true if the given module should be applied to the incoming request.
|
||||||
* @param {Request} request
|
* @param {Request} request
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import {HTTPKernelModule} from "../HTTPKernelModule";
|
||||||
|
import {ResponseObject} from "../../routing/Route";
|
||||||
|
import {Request} from "../../lifecycle/Request";
|
||||||
|
import {plaintext} from "../../response/StringResponseFactory";
|
||||||
|
import {ResponseFactory} from "../../response/ResponseFactory";
|
||||||
|
import {json} from "../../response/JSONResponseFactory";
|
||||||
|
|
||||||
|
export abstract class AbstractResolvedRouteHandlerHTTPModule extends HTTPKernelModule {
|
||||||
|
protected async applyResponseObject(object: ResponseObject, request: Request) {
|
||||||
|
if ( (typeof object === 'string') || (typeof object === 'number') ) {
|
||||||
|
object = plaintext(String(object))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( object instanceof ResponseFactory ) {
|
||||||
|
await object.write(request)
|
||||||
|
} else if ( typeof object !== 'undefined' ) {
|
||||||
|
await json(object).write(request)
|
||||||
|
} else {
|
||||||
|
await plaintext('').write(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,12 @@
|
|||||||
import {HTTPKernelModule} from "../HTTPKernelModule";
|
|
||||||
import {HTTPKernel} from "../HTTPKernel";
|
import {HTTPKernel} from "../HTTPKernel";
|
||||||
import {Request} from "../../lifecycle/Request";
|
import {Request} from "../../lifecycle/Request";
|
||||||
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
||||||
import {ResponseObject} from "../../routing/Route";
|
import {ResponseObject} from "../../routing/Route";
|
||||||
import {plaintext} from "../../response/StringResponseFactory";
|
|
||||||
import {ResponseFactory} from "../../response/ResponseFactory";
|
|
||||||
import {json} from "../../response/JSONResponseFactory";
|
|
||||||
import {http} from "../../response/HTTPErrorResponseFactory";
|
import {http} from "../../response/HTTPErrorResponseFactory";
|
||||||
import {HTTPStatus} from "@extollo/util";
|
import {HTTPStatus} from "@extollo/util";
|
||||||
|
import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHandlerHTTPModule";
|
||||||
|
|
||||||
export class ExecuteResolvedRouteHandlerHTTPModule extends HTTPKernelModule {
|
export class ExecuteResolvedRouteHandlerHTTPModule extends AbstractResolvedRouteHandlerHTTPModule {
|
||||||
public static register(kernel: HTTPKernel) {
|
public static register(kernel: HTTPKernel) {
|
||||||
kernel.register(this).core()
|
kernel.register(this).core()
|
||||||
}
|
}
|
||||||
@ -19,19 +16,10 @@ export class ExecuteResolvedRouteHandlerHTTPModule extends HTTPKernelModule {
|
|||||||
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
||||||
let object: ResponseObject = await route.handler(request)
|
let object: ResponseObject = await route.handler(request)
|
||||||
|
|
||||||
if ( (typeof object === 'string') || (typeof object === 'number') ) {
|
await this.applyResponseObject(object, request)
|
||||||
object = plaintext(String(object))
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( object instanceof ResponseFactory ) {
|
|
||||||
await object.write(request)
|
|
||||||
} else if ( typeof object !== 'undefined' ) {
|
|
||||||
await json(object).write(request)
|
|
||||||
} else {
|
|
||||||
await plaintext('').write(request)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
await http(HTTPStatus.NOT_FOUND).write(request)
|
await http(HTTPStatus.NOT_FOUND).write(request)
|
||||||
|
request.response.blockingWriteback(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return request
|
return request
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import {HTTPKernel} from "../HTTPKernel";
|
||||||
|
import {Request} from "../../lifecycle/Request";
|
||||||
|
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
||||||
|
import {ResponseObject} from "../../routing/Route";
|
||||||
|
import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHandlerHTTPModule";
|
||||||
|
import {PersistSessionHTTPModule} from "./PersistSessionHTTPModule";
|
||||||
|
|
||||||
|
export class ExecuteResolvedRoutePostflightHTTPModule extends AbstractResolvedRouteHandlerHTTPModule {
|
||||||
|
public static register(kernel: HTTPKernel) {
|
||||||
|
kernel.register(this).before(PersistSessionHTTPModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async apply(request: Request) {
|
||||||
|
if ( request.hasInstance(ActivatedRoute) ) {
|
||||||
|
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
||||||
|
const postflight = route.postflight
|
||||||
|
|
||||||
|
for ( const handler of postflight ) {
|
||||||
|
const result: ResponseObject = await handler(request)
|
||||||
|
if ( typeof result !== "undefined" ) {
|
||||||
|
await this.applyResponseObject(result, request)
|
||||||
|
request.response.blockingWriteback(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
import {HTTPKernel} from "../HTTPKernel";
|
||||||
|
import {MountActivatedRouteHTTPModule} from "./MountActivatedRouteHTTPModule";
|
||||||
|
import {Request} from "../../lifecycle/Request";
|
||||||
|
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
||||||
|
import {ResponseObject} from "../../routing/Route";
|
||||||
|
import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHandlerHTTPModule";
|
||||||
|
|
||||||
|
export class ExecuteResolvedRoutePreflightHTTPModule extends AbstractResolvedRouteHandlerHTTPModule {
|
||||||
|
public static register(kernel: HTTPKernel) {
|
||||||
|
kernel.register(this).after(MountActivatedRouteHTTPModule)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async apply(request: Request) {
|
||||||
|
if ( request.hasInstance(ActivatedRoute) ) {
|
||||||
|
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
||||||
|
const preflight = route.preflight
|
||||||
|
|
||||||
|
for ( const handler of preflight ) {
|
||||||
|
const result: ResponseObject = await handler(request)
|
||||||
|
if ( typeof result !== "undefined" ) {
|
||||||
|
await this.applyResponseObject(result, request)
|
||||||
|
request.response.blockingWriteback(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ import {Session} from "../../session/Session";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InjectSessionHTTPModule extends HTTPKernelModule {
|
export class InjectSessionHTTPModule extends HTTPKernelModule {
|
||||||
|
public readonly executeWithBlockingWriteback = true
|
||||||
|
|
||||||
public static register(kernel: HTTPKernel) {
|
public static register(kernel: HTTPKernel) {
|
||||||
kernel.register(this).after(SetSessionCookieHTTPModule)
|
kernel.register(this).after(SetSessionCookieHTTPModule)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ import {Logging} from "../../../service/Logging";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MountActivatedRouteHTTPModule extends HTTPKernelModule {
|
export class MountActivatedRouteHTTPModule extends HTTPKernelModule {
|
||||||
|
public readonly executeWithBlockingWriteback = true
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly routing!: Routing
|
protected readonly routing!: Routing
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import {Session} from "../../session/Session";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PersistSessionHTTPModule extends HTTPKernelModule {
|
export class PersistSessionHTTPModule extends HTTPKernelModule {
|
||||||
|
public readonly executeWithBlockingWriteback = true
|
||||||
|
|
||||||
public static register(kernel: HTTPKernel) {
|
public static register(kernel: HTTPKernel) {
|
||||||
kernel.register(this).last()
|
kernel.register(this).last()
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import {Config} from "../../../service/Config";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PoweredByHeaderInjectionHTTPModule extends HTTPKernelModule {
|
export class PoweredByHeaderInjectionHTTPModule extends HTTPKernelModule {
|
||||||
|
public readonly executeWithBlockingWriteback = true
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly config!: Config;
|
protected readonly config!: Config;
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ import {Logging} from "../../../service/Logging";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SetSessionCookieHTTPModule extends HTTPKernelModule {
|
export class SetSessionCookieHTTPModule extends HTTPKernelModule {
|
||||||
|
public readonly executeWithBlockingWriteback = true
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly logging!: Logging
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ export class Response {
|
|||||||
private _sentHeaders: boolean = false
|
private _sentHeaders: boolean = false
|
||||||
private _responseEnded: boolean = false
|
private _responseEnded: boolean = false
|
||||||
private _status: HTTPStatus = HTTPStatus.OK
|
private _status: HTTPStatus = HTTPStatus.OK
|
||||||
|
private _blockingWriteback: boolean = false
|
||||||
public body: string = ''
|
public body: string = ''
|
||||||
public readonly sending$: BehaviorSubject<Response> = new BehaviorSubject<Response>()
|
public readonly sending$: BehaviorSubject<Response> = new BehaviorSubject<Response>()
|
||||||
public readonly sent$: BehaviorSubject<Response> = new BehaviorSubject<Response>()
|
public readonly sent$: BehaviorSubject<Response> = new BehaviorSubject<Response>()
|
||||||
@ -88,6 +89,18 @@ export class Response {
|
|||||||
return this._sentHeaders
|
return this._sentHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasBody() {
|
||||||
|
return !!this.body
|
||||||
|
}
|
||||||
|
|
||||||
|
public blockingWriteback(set?: boolean) {
|
||||||
|
if ( typeof set !== 'undefined' ) {
|
||||||
|
this._blockingWriteback = set
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._blockingWriteback
|
||||||
|
}
|
||||||
|
|
||||||
public async write(data: any) {
|
public async write(data: any) {
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<void>((res, rej) => {
|
||||||
if ( !this._sentHeaders ) this.sendHeaders()
|
if ( !this._sentHeaders ) this.sendHeaders()
|
||||||
|
@ -4,6 +4,8 @@ import {ResolvedRouteHandler, Route} from "./Route";
|
|||||||
export class ActivatedRoute {
|
export class ActivatedRoute {
|
||||||
public readonly params: {[key: string]: string}
|
public readonly params: {[key: string]: string}
|
||||||
public readonly handler: ResolvedRouteHandler
|
public readonly handler: ResolvedRouteHandler
|
||||||
|
public readonly preflight: ResolvedRouteHandler[]
|
||||||
|
public readonly postflight: ResolvedRouteHandler[]
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly route: Route,
|
public readonly route: Route,
|
||||||
@ -20,6 +22,8 @@ export class ActivatedRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.params = params
|
this.params = params
|
||||||
|
this.preflight = route.resolvePreflight()
|
||||||
this.handler = route.resolveHandler()
|
this.handler = route.resolveHandler()
|
||||||
|
this.postflight = route.resolvePostflight()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
src/http/routing/Middleware.ts
Normal file
16
src/http/routing/Middleware.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import {AppClass} from "../../lifecycle/AppClass"
|
||||||
|
import {Request} from "../lifecycle/Request"
|
||||||
|
import {ResponseObject} from "./Route"
|
||||||
|
|
||||||
|
export abstract class Middleware extends AppClass {
|
||||||
|
constructor(
|
||||||
|
protected readonly request: Request
|
||||||
|
) { super() }
|
||||||
|
|
||||||
|
protected container() {
|
||||||
|
return this.request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return void | Promise<void> to continue request
|
||||||
|
public abstract apply(): ResponseObject
|
||||||
|
}
|
@ -5,15 +5,17 @@ import {RouteGroup} from "./RouteGroup";
|
|||||||
import {ResponseFactory} from "../response/ResponseFactory";
|
import {ResponseFactory} from "../response/ResponseFactory";
|
||||||
import {Response} from "../lifecycle/Response";
|
import {Response} from "../lifecycle/Response";
|
||||||
import {Controllers} from "../../service/Controllers";
|
import {Controllers} from "../../service/Controllers";
|
||||||
import {ErrorWithContext} from "@extollo/util";
|
import {ErrorWithContext, Collection} from "@extollo/util";
|
||||||
import {Controller} from "../Controller";
|
import {Controller} from "../Controller";
|
||||||
|
import {Middlewares} from "../../service/Middlewares";
|
||||||
|
import {Middleware} from "./Middleware";
|
||||||
|
|
||||||
export type ResponseObject = ResponseFactory | string | number | void | any | Promise<ResponseObject>
|
export type ResponseObject = ResponseFactory | string | number | void | any | Promise<ResponseObject>
|
||||||
export type RouteHandler = ((request: Request, response: Response) => ResponseObject) | ((request: Request) => ResponseObject) | (() => ResponseObject) | string
|
export type RouteHandler = ((request: Request, response: Response) => ResponseObject) | ((request: Request) => ResponseObject) | (() => ResponseObject) | string
|
||||||
export type ResolvedRouteHandler = (request: Request) => ResponseObject
|
export type ResolvedRouteHandler = (request: Request) => ResponseObject
|
||||||
|
|
||||||
|
|
||||||
// TODO middleware, domains, named routes - support this on groups as well
|
// TODO domains, named routes - support this on groups as well
|
||||||
|
|
||||||
export class Route extends AppClass {
|
export class Route extends AppClass {
|
||||||
private static registeredRoutes: Route[] = []
|
private static registeredRoutes: Route[] = []
|
||||||
@ -36,9 +38,18 @@ export class Route extends AppClass {
|
|||||||
for ( const route of registeredRoutes ) {
|
for ( const route of registeredRoutes ) {
|
||||||
for ( const group of stack ) {
|
for ( const group of stack ) {
|
||||||
route.prepend(group.prefix)
|
route.prepend(group.prefix)
|
||||||
|
group.getGroupMiddlewareDefinitions()
|
||||||
|
.each(def => route.prependMiddleware(def))
|
||||||
}
|
}
|
||||||
|
|
||||||
route.resolveHandler() // Try to resolve here to catch any errors at boot-time
|
for ( const group of this.compiledGroupStack ) {
|
||||||
|
group.getGroupMiddlewareDefinitions()
|
||||||
|
.each(def => route.appendMiddleware(def))
|
||||||
|
}
|
||||||
|
|
||||||
|
route.resolvePreflight() // Try to resolve here to catch any errors at boot-time and pre-compile
|
||||||
|
route.resolveHandler()
|
||||||
|
route.resolvePostflight()
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( const group of registeredGroups ) {
|
for ( const group of registeredGroups ) {
|
||||||
@ -90,6 +101,11 @@ export class Route extends AppClass {
|
|||||||
return grp
|
return grp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected middlewares: Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> = new Collection<{stage: "pre" | "post"; handler: RouteHandler}>()
|
||||||
|
protected _compiledPreflight?: ResolvedRouteHandler[]
|
||||||
|
protected _compiledHandler?: ResolvedRouteHandler
|
||||||
|
protected _compiledPostflight?: ResolvedRouteHandler[]
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected method: HTTPMethod | HTTPMethod[],
|
protected method: HTTPMethod | HTTPMethod[],
|
||||||
protected readonly handler: RouteHandler,
|
protected readonly handler: RouteHandler,
|
||||||
@ -138,7 +154,63 @@ export class Route extends AppClass {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public resolvePreflight(): ResolvedRouteHandler[] {
|
||||||
|
if ( !this._compiledPreflight ) {
|
||||||
|
this._compiledPreflight = this.resolveMiddlewareHandlersForStage('pre')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._compiledPreflight
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolvePostflight(): ResolvedRouteHandler[] {
|
||||||
|
if ( !this._compiledPostflight ) {
|
||||||
|
this._compiledPostflight = this.resolveMiddlewareHandlersForStage('post')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._compiledPostflight
|
||||||
|
}
|
||||||
|
|
||||||
public resolveHandler(): ResolvedRouteHandler {
|
public resolveHandler(): ResolvedRouteHandler {
|
||||||
|
if ( !this._compiledHandler ) {
|
||||||
|
this._compiledHandler = this._resolveHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._compiledHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
pre(middleware: RouteHandler) {
|
||||||
|
this.middlewares.push({
|
||||||
|
stage: 'pre',
|
||||||
|
handler: middleware
|
||||||
|
})
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
post(middleware: RouteHandler) {
|
||||||
|
this.middlewares.push({
|
||||||
|
stage: 'post',
|
||||||
|
handler: middleware,
|
||||||
|
})
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepend(prefix: string) {
|
||||||
|
if ( !prefix.endsWith('/') ) prefix = `${prefix}/`
|
||||||
|
if ( this.route.startsWith('/') ) this.route = this.route.substring(1)
|
||||||
|
this.route = `${prefix}${this.route}`
|
||||||
|
}
|
||||||
|
|
||||||
|
private prependMiddleware(def: { stage: 'pre' | 'post', handler: RouteHandler }) {
|
||||||
|
this.middlewares.prepend(def)
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendMiddleware(def: { stage: 'pre' | 'post', handler: RouteHandler }) {
|
||||||
|
this.middlewares.push(def)
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resolveHandler(): ResolvedRouteHandler {
|
||||||
if ( typeof this.handler !== 'string' ) {
|
if ( typeof this.handler !== 'string' ) {
|
||||||
return (request: Request) => {
|
return (request: Request) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -146,7 +218,7 @@ export class Route extends AppClass {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const parts = this.handler.split('.')
|
const parts = this.handler.split('.')
|
||||||
if ( parts.length < 1 ) {
|
if ( parts.length < 2 ) {
|
||||||
const e = new ErrorWithContext('Route handler does not specify a method name.')
|
const e = new ErrorWithContext('Route handler does not specify a method name.')
|
||||||
e.context = {
|
e.context = {
|
||||||
handler: this.handler
|
handler: this.handler
|
||||||
@ -179,10 +251,45 @@ export class Route extends AppClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private prepend(prefix: string) {
|
private resolveMiddlewareHandlersForStage(stage: 'pre' | 'post'): ResolvedRouteHandler[] {
|
||||||
if ( !prefix.endsWith('/') ) prefix = `${prefix}/`
|
return this.middlewares.where('stage', '=', stage)
|
||||||
if ( this.route.startsWith('/') ) this.route = this.route.substring(1)
|
.map<ResolvedRouteHandler>(def => {
|
||||||
this.route = `${prefix}${this.route}`
|
if ( typeof def.handler !== 'string' ) {
|
||||||
|
return (request: Request) => {
|
||||||
|
// @ts-ignore
|
||||||
|
return def.handler(request, request.response)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const parts = def.handler.split('.')
|
||||||
|
if ( parts.length < 2 ) {
|
||||||
|
parts.push('apply') // default middleware method name, if none provided
|
||||||
|
}
|
||||||
|
|
||||||
|
const [middlewareName, methodName] = parts
|
||||||
|
|
||||||
|
const middlewaresService = <Middlewares> this.make(Middlewares)
|
||||||
|
const middlewareClass = middlewaresService.get(middlewareName)
|
||||||
|
if ( !middlewareClass ) {
|
||||||
|
const e = new ErrorWithContext('Middleware not found for route handler.')
|
||||||
|
e.context = {
|
||||||
|
handler: def.handler,
|
||||||
|
middlewareName,
|
||||||
|
methodName,
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
return (request: Request) => {
|
||||||
|
// If not a function, then we got a string reference to a middleware method
|
||||||
|
// So, we need to use the request container to instantiate the middleware
|
||||||
|
// and bind the method
|
||||||
|
const middleware = <Middleware> request.make(middlewareClass, request)
|
||||||
|
const method = middleware.getBoundMethod(methodName)
|
||||||
|
return method()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import {AppClass} from "../../lifecycle/AppClass";
|
import {Collection} from "@extollo/util"
|
||||||
|
import {AppClass} from "../../lifecycle/AppClass"
|
||||||
|
import {RouteHandler} from "./Route"
|
||||||
|
|
||||||
export class RouteGroup extends AppClass {
|
export class RouteGroup extends AppClass {
|
||||||
private static currentGroupNesting: RouteGroup[] = []
|
private static currentGroupNesting: RouteGroup[] = []
|
||||||
|
|
||||||
|
protected middlewares: Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> = new Collection<{stage: "pre" | "post"; handler: RouteHandler}>()
|
||||||
|
|
||||||
public static getCurrentGroupHierarchy(): RouteGroup[] {
|
public static getCurrentGroupHierarchy(): RouteGroup[] {
|
||||||
return [...this.currentGroupNesting]
|
return [...this.currentGroupNesting]
|
||||||
}
|
}
|
||||||
@ -11,4 +15,26 @@ export class RouteGroup extends AppClass {
|
|||||||
public readonly group: () => void | Promise<void>,
|
public readonly group: () => void | Promise<void>,
|
||||||
public readonly prefix: string
|
public readonly prefix: string
|
||||||
) { super() }
|
) { super() }
|
||||||
|
|
||||||
|
pre(middleware: RouteHandler) {
|
||||||
|
this.middlewares.push({
|
||||||
|
stage: 'pre',
|
||||||
|
handler: middleware
|
||||||
|
})
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
post(middleware: RouteHandler) {
|
||||||
|
this.middlewares.push({
|
||||||
|
stage: 'post',
|
||||||
|
handler: middleware,
|
||||||
|
})
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
getGroupMiddlewareDefinitions() {
|
||||||
|
return this.middlewares
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,10 @@ export * from './http/kernel/module/MountActivatedRouteHTTPModule'
|
|||||||
export * from './http/kernel/module/PersistSessionHTTPModule'
|
export * from './http/kernel/module/PersistSessionHTTPModule'
|
||||||
export * from './http/kernel/module/PoweredByHeaderInjectionHTTPModule'
|
export * from './http/kernel/module/PoweredByHeaderInjectionHTTPModule'
|
||||||
export * from './http/kernel/module/SetSessionCookieHTTPModule'
|
export * from './http/kernel/module/SetSessionCookieHTTPModule'
|
||||||
|
export * from './http/kernel/module/AbstractResolvedRouteHandlerHTTPModule'
|
||||||
|
export * from './http/kernel/module/ExecuteResolvedRoutePreflightHTTPModule'
|
||||||
|
export * from './http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule'
|
||||||
|
export * from './http/kernel/module/ExecuteResolvedRoutePostflightHTTPModule'
|
||||||
|
|
||||||
export * from './http/kernel/HTTPKernel'
|
export * from './http/kernel/HTTPKernel'
|
||||||
export * from './http/kernel/HTTPKernelModule'
|
export * from './http/kernel/HTTPKernelModule'
|
||||||
@ -34,6 +38,7 @@ export * from './http/response/ViewResponseFactory'
|
|||||||
export * from './http/routing/ActivatedRoute'
|
export * from './http/routing/ActivatedRoute'
|
||||||
export * from './http/routing/Route'
|
export * from './http/routing/Route'
|
||||||
export * from './http/routing/RouteGroup'
|
export * from './http/routing/RouteGroup'
|
||||||
|
export * from './http/routing/Middleware'
|
||||||
|
|
||||||
export * from './http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule'
|
export * from './http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule'
|
||||||
|
|
||||||
@ -52,6 +57,7 @@ export * from './service/Config'
|
|||||||
export * from './service/Controllers'
|
export * from './service/Controllers'
|
||||||
export * from './service/HTTPServer'
|
export * from './service/HTTPServer'
|
||||||
export * from './service/Routing'
|
export * from './service/Routing'
|
||||||
|
export * from './service/Middlewares'
|
||||||
|
|
||||||
export * from './views/ViewEngine'
|
export * from './views/ViewEngine'
|
||||||
export * from './views/ViewEngineFactory'
|
export * from './views/ViewEngineFactory'
|
||||||
|
@ -12,6 +12,8 @@ import {PersistSessionHTTPModule} from "../http/kernel/module/PersistSessionHTTP
|
|||||||
import {MountActivatedRouteHTTPModule} from "../http/kernel/module/MountActivatedRouteHTTPModule";
|
import {MountActivatedRouteHTTPModule} from "../http/kernel/module/MountActivatedRouteHTTPModule";
|
||||||
import {ExecuteResolvedRouteHandlerHTTPModule} from "../http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule";
|
import {ExecuteResolvedRouteHandlerHTTPModule} from "../http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule";
|
||||||
import {error} from "../http/response/ErrorResponseFactory";
|
import {error} from "../http/response/ErrorResponseFactory";
|
||||||
|
import {ExecuteResolvedRoutePreflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePreflightHTTPModule";
|
||||||
|
import {ExecuteResolvedRoutePostflightHTTPModule} from "../http/kernel/module/ExecuteResolvedRoutePostflightHTTPModule";
|
||||||
|
|
||||||
@Singleton()
|
@Singleton()
|
||||||
export class HTTPServer extends Unit {
|
export class HTTPServer extends Unit {
|
||||||
@ -33,6 +35,8 @@ export class HTTPServer extends Unit {
|
|||||||
PersistSessionHTTPModule.register(this.kernel)
|
PersistSessionHTTPModule.register(this.kernel)
|
||||||
MountActivatedRouteHTTPModule.register(this.kernel)
|
MountActivatedRouteHTTPModule.register(this.kernel)
|
||||||
ExecuteResolvedRouteHandlerHTTPModule.register(this.kernel)
|
ExecuteResolvedRouteHandlerHTTPModule.register(this.kernel)
|
||||||
|
ExecuteResolvedRoutePreflightHTTPModule.register(this.kernel)
|
||||||
|
ExecuteResolvedRoutePostflightHTTPModule.register(this.kernel)
|
||||||
|
|
||||||
await new Promise<void>((res, rej) => {
|
await new Promise<void>((res, rej) => {
|
||||||
this.server = createServer(this.handler)
|
this.server = createServer(this.handler)
|
||||||
|
20
src/service/Middlewares.ts
Normal file
20
src/service/Middlewares.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {CanonicalStatic} from "./CanonicalStatic";
|
||||||
|
import {Singleton, Instantiable} from "@extollo/di";
|
||||||
|
import {CanonicalDefinition} from "./Canonical";
|
||||||
|
import {Middleware} from "../http/routing/Middleware";
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class Middlewares extends CanonicalStatic<Instantiable<Middleware>, Middleware> {
|
||||||
|
protected appPath = ['http', 'middlewares']
|
||||||
|
protected canonicalItem = 'middleware'
|
||||||
|
protected suffix = '.middleware.js'
|
||||||
|
|
||||||
|
public async initCanonicalItem(definition: CanonicalDefinition) {
|
||||||
|
const item = await super.initCanonicalItem(definition)
|
||||||
|
if ( !(item.prototype instanceof Middleware) ) {
|
||||||
|
throw new TypeError(`Invalid middleware definition: ${definition.originalName}. Controllers must extend from @extollo/lib.http.routing.Middleware.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user