From acb00894f96e0be3dde09cc3ba643d4a3ad7a2bc Mon Sep 17 00:00:00 2001 From: Hage Yaapa Date: Mon, 15 Oct 2018 10:03:54 +0530 Subject: [PATCH] fix: optimize serving static files Optimize serving static files --- packages/rest/src/rest.server.ts | 28 ++++++++++++----- packages/rest/src/router/router-base.ts | 5 +++ packages/rest/src/router/routing-table.ts | 37 ++++++++++++++++++++--- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index b4d4ec44b152..fe4b282aa4dc 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -33,6 +33,7 @@ import { Route, RouteEntry, RoutingTable, + StaticRoute, } from './router/routing-table'; import {DefaultSequence, SequenceFunction, SequenceHandler} from './sequence'; @@ -46,6 +47,7 @@ import { Send, } from './types'; import {ServerOptions} from 'https'; +import * as HttpErrors from 'http-errors'; const debug = require('debug')('loopback:rest:server'); @@ -190,6 +192,25 @@ export class RestServer extends Context implements Server, HttpServerLike { this._setupRequestHandler(); this.bind(RestBindings.HANDLER).toDynamicValue(() => this.httpHandler); + + // LB4's static assets serving router + const staticAssetsRouter = new StaticRoute( + (req: Request, res: Response) => { + return new Promise((resolve, reject) => { + const onFinished = () => resolve(); + res.once('finish', onFinished); + this._routerForStaticAssets.handle(req, res, (err: Error) => { + if (err) { + return reject(err); + } + // Express router called next, which means no route was matched + return reject(new HttpErrors.NotFound()); + }); + }); + }, + ); + + this.httpHandler.registerRoute(staticAssetsRouter); } protected _setupRequestHandler() { @@ -243,7 +264,6 @@ export class RestServer extends Context implements Server, HttpServerLike { protected _setupRouterForStaticAssets() { if (!this._routerForStaticAssets) { this._routerForStaticAssets = express.Router(); - this._expressApp.use(this._routerForStaticAssets); } } @@ -597,12 +617,6 @@ export class RestServer extends Context implements Server, HttpServerLike { * @param options Options for serve-static */ static(path: PathParams, rootDir: string, options?: ServeStaticOptions) { - const re = pathToRegExp(path, [], {end: false}); - if (re.test('/')) { - throw new Error( - 'Static assets cannot be mount to "/" to avoid performance penalty.', - ); - } this._routerForStaticAssets.use(path, express.static(rootDir, options)); } diff --git a/packages/rest/src/router/router-base.ts b/packages/rest/src/router/router-base.ts index cc600c07be6a..8738edc60871 100644 --- a/packages/rest/src/router/router-base.ts +++ b/packages/rest/src/router/router-base.ts @@ -49,6 +49,11 @@ export abstract class BaseRouter implements RestRouter { else return this.findRouteWithPathVars(request); } + getStaticAssetsRouter() { + const route = this.routesWithoutPathVars['/get/*']; + return createResolvedRoute(route, {}); + } + list() { let routes = Object.values(this.routesWithoutPathVars); routes = routes.concat(this.listRoutesWithPathVars()); diff --git a/packages/rest/src/router/routing-table.ts b/packages/rest/src/router/routing-table.ts index cbc09ec926c8..1d4828e59b4d 100644 --- a/packages/rest/src/router/routing-table.ts +++ b/packages/rest/src/router/routing-table.ts @@ -27,6 +27,8 @@ import { OperationRetval, } from '../types'; +import {RestBindings} from '../keys'; + import {ControllerSpec} from '@loopback/openapi-v3'; import * as assert from 'assert'; @@ -72,6 +74,8 @@ export interface RestRouter { */ find(request: Request): ResolvedRoute | undefined; + getStaticAssetsRouter(): ResolvedRoute; + /** * List all routes */ @@ -180,10 +184,8 @@ export class RoutingTable { return found; } - debug('No route found for %s %s', request.method, request.path); - throw new HttpErrors.NotFound( - `Endpoint "${request.method} ${request.path}" not found.`, - ); + const staticAssetsRouter = this._router.getStaticAssetsRouter(); + return staticAssetsRouter; } } @@ -238,6 +240,31 @@ export interface ResolvedRoute extends RouteEntry { readonly schemas: SchemasObject; } +export class StaticRoute implements RouteEntry { + public readonly verb: string = 'get'; + public readonly path: string = '*'; + public readonly spec: OperationObject = {responses: {}}; + + constructor(private _handler: Function) {} + + updateBindings(requestContext: Context): void { + // no-op + } + + async invokeHandler( + requestContext: Context, + args: OperationArgs, + ): Promise { + const req = await requestContext.get(RestBindings.Http.REQUEST); + const res = await requestContext.get(RestBindings.Http.RESPONSE); + return this._handler(req, res); + } + + describe(): string { + return 'final route to handle static assets'; + } +} + /** * Base implementation of RouteEntry */ @@ -290,7 +317,7 @@ export class Route extends BaseRoute { verb: string, path: string, public readonly spec: OperationObject, - protected readonly _handler: Function, + protected readonly _handler: Function, // <-- doesn't this Function have a signature? ) { super(verb, path, spec); }