import * as express from 'express' ; import * as inversify from 'inversify' ; import { OutgoingHttpHeaders } from 'http' ; import * as interfaces from './interfaces' ; import { BaseMiddleware } from './index' ; import { getControllersFromMetadata , getControllersFromContainer , getControllerMetadata , getControllerMethodMetadata , getControllerParameterMetadata , instanceOfIHttpActionResult , } from './utils' ; import { TYPE , METADATA_KEY , DEFAULT_ROUTING_ROOT_PATH , PARAMETER_TYPE , DUPLICATED_CONTROLLER_NAME , } from './constants' ; import { HttpResponseMessage } from './httpResponseMessage' ; export class InversifyExpressServer { private _router : express . Router ; private _container : inversify . interfaces . Container ; private _app : express . Application ; private _configFn ! : interfaces . ConfigFunction ; private _errorConfigFn ! : interfaces . ConfigFunction ; private _routingConfig : interfaces . RoutingConfig ; private _AuthProvider ! : new ( ) => interfaces . AuthProvider ; private _forceControllers : boolean ; param container param customRouter param routingConfig param customApp param authProvider param forceControllers constructor ( container , customRouter ? , routingConfig ? , customApp ? , authProvider ? , forceControllers = true ) { this . _container = container ; this . _forceControllers = forceControllers ; this . _router = customRouter || express . Router ( ) ; this . _routingConfig = routingConfig || { rootPath : DEFAULT_ROUTING_ROOT_PATH , } ; this . _app = customApp || express ( ) ; if ( authProvider ) { this . _AuthProvider = authProvider ; container . bind < interfaces . AuthProvider > ( TYPE . AuthProvider ) . to ( this . _AuthProvider ) ; } } param fn public setConfig ( fn ) { this . _configFn = fn ; return this ; } param fn public setErrorConfig ( fn ) { this . _errorConfigFn = fn ; return this ; } /**
     * Applies all routes and configuration to the server, returning the express application.
     */ public build ( ) { this . _app . all ( '*' , ( req , res , next ) => { ( async ( ) => { const httpContext = await this . _createHttpContext ( req , res , next ) ; Reflect . defineMetadata ( METADATA_KEY . httpContext , httpContext , req ) ; next ( ) ; } ) ( ) ; } ) ; if ( this . _configFn ) { this . _configFn . apply ( undefined , [ this . _app ] ) ; } this . registerControllers ( ) ; if ( this . _errorConfigFn ) { this . _errorConfigFn . apply ( undefined , [ this . _app ] ) ; } return this . _app ; } private registerControllers ( ) { this . _container . bind < interfaces . HttpContext > ( TYPE . HttpContext ) . toConstantValue ( { } as any ) ; const constructors = getControllersFromMetadata ( ) ; constructors . forEach ( constructor => { const { name } = constructor ; if ( this . _container . isBoundNamed ( TYPE . Controller , name ) ) { throw new Error ( DUPLICATED_CONTROLLER_NAME ( name ) ) ; } this . _container . bind ( TYPE . Controller ) . to ( constructor ) . whenTargetNamed ( name ) ; } ) ; const controllers = getControllersFromContainer ( this . _container , this . _forceControllers ) ; controllers . forEach ( ( controller ) => { const controllerMetadata = getControllerMetadata ( controller . constructor ) ; const methodMetadata = getControllerMethodMetadata ( controller . constructor ) ; const parameterMetadata = getControllerParameterMetadata ( controller . constructor ) ; if ( controllerMetadata && methodMetadata ) { const controllerMiddleware = this . resolveMidleware ( ... controllerMetadata . middleware ) ; methodMetadata . forEach ( ( metadata ) => { let paramList = [ ] ; if ( parameterMetadata ) { paramList = parameterMetadata [ metadata . key ] || [ ] ; } const handler = this . handlerFactory ( controllerMetadata . target . name , metadata . key , paramList ) ; const routeMiddleware = this . resolveMidleware ( ... metadata . middleware ) ; this . _router [ metadata . method ] ( `${ controllerMetadata . path }${ metadata . path }` , ... controllerMiddleware , ... routeMiddleware , handler ) ; } ) ; } } ) ; this . _app . use ( this . _routingConfig . rootPath , this . _router ) ; } private resolveMidleware ( ... middleware ) { return middleware . map ( middlewareItem => { if ( ! this . _container . isBound ( middlewareItem ) ) { return middlewareItem as express . RequestHandler ; } type MiddlewareInstance = express . RequestHandler | BaseMiddleware ; const m = this . _container . get < MiddlewareInstance > ( middlewareItem ) ; if ( m instanceof BaseMiddleware ) { return ( req , res , next ) : void => { const mReq = this . _container . get < BaseMiddleware > ( middlewareItem ) ; ( mReq ) . httpContext = this . _getHttpContext ( req ) ; mReq . handler ( req , res , next ) ; } ; } return m ; } ) ; } private copyHeadersTo ( headers , target ) { for ( const name of Object . keys ( headers ) ) { const headerValue = headers [ name ] ; target . append ( name , typeof headerValue === 'number' ? headerValue . toString ( ) : headerValue ) ; } } private async handleHttpResponseMessage ( message , res ) { this . copyHeadersTo ( message . headers , res ) ; if ( message . content !== undefined ) { this . copyHeadersTo ( message . content . headers , res ) ; res . status ( message . statusCode ) . send ( await message . content . readAsStringAsync ( ) ) ; } else { res . sendStatus ( message . statusCode ) ; } } private handlerFactory ( controllerName , key , parameterMetadata ) { return async ( req , res , next ) => { try { const args = this . extractParameters ( req , res , next , parameterMetadata ) ; const httpContext = this . _getHttpContext ( req ) ; httpContext . container . bind < interfaces . HttpContext > ( TYPE . HttpContext ) . toConstantValue ( httpContext ) ; const value = await httpContext . container . getNamed < any > ( TYPE . Controller , controllerName ) [ key ] ( ... args ) ; if ( value instanceof HttpResponseMessage ) { await this . handleHttpResponseMessage ( value , res ) ; } else if ( instanceOfIHttpActionResult ( value ) ) { const httpResponseMessage = await value . executeAsync ( ) ; await this . handleHttpResponseMessage ( httpResponseMessage , res ) ; } else if ( value instanceof Function ) { value ( ) ; } else if ( ! res . headersSent ) { if ( value === undefined ) { res . status ( 204 ) ; } res . send ( value ) ; } } catch ( err ) { next ( err ) ; } } ; } private _getHttpContext ( req ) { return Reflect . getMetadata ( METADATA_KEY . httpContext , req ) ; } private async _createHttpContext ( req , res , next ) { const principal = await this . _getCurrentUser ( req , res , next ) ; return { request : req , response : res , container : this . _container . createChild ( ) , user : principal , } ; } private async _getCurrentUser ( req , res , next ) { if ( this . _AuthProvider !== undefined ) { const authProvider = this . _container . get < interfaces . AuthProvider > ( TYPE . AuthProvider ) ; return authProvider . getUser ( req , res , next ) ; } return Promise . resolve < interfaces . Principal > ( { details : null , isAuthenticated : ( ) => Promise . resolve ( false ) , isInRole : ( role ) => Promise . resolve ( false ) , isResourceOwner : ( resourceId ) => Promise . resolve ( false ) , } ) ; } private extractParameters ( req , res , next , params ) { const args = [ ] ; if ( ! params || ! params . length ) { return [ req , res , next ] ; } params . forEach ( ( { type , index , parameterName , injectRoot , } ) => { switch ( type ) { case PARAMETER_TYPE . REQUEST : args [ index ] = req ; break ; case PARAMETER_TYPE . NEXT : args [ index ] = next ; break ; case PARAMETER_TYPE . PARAMS : args [ index ] = this . getParam ( req , 'params' , injectRoot , parameterName ) ; break ; case PARAMETER_TYPE . QUERY : args [ index ] = this . getParam ( req , 'query' , injectRoot , parameterName ) ; break ; case PARAMETER_TYPE . BODY : args [ index ] = req . body ; break ; case PARAMETER_TYPE . HEADERS : args [ index ] = this . getParam ( req , 'headers' , injectRoot , parameterName ) ; break ; case PARAMETER_TYPE . COOKIES : args [ index ] = this . getParam ( req , 'cookies' , injectRoot , parameterName ) ; break ; case PARAMETER_TYPE . PRINCIPAL : args [ index ] = this . _getPrincipal ( req ) ; break ; default : args [ index ] = res ; break ; } } ) ; args . push ( req , res , next ) ; return args ; } private getParam ( source , paramType , injectRoot , name ? ) { const key = paramType === 'headers' ? name ?. toLowerCase ( ) : name ; const param = source [ paramType ] ; if ( injectRoot ) { return param ; } return ( param && key ) ? param [ key ] : undefined ; } private _getPrincipal ( req ) { return this . _getHttpContext ( req ) . user ; } }