UNPKG

@loopback/rest

Version:

Expose controllers as REST endpoints and route REST API requests to controller methods

242 lines 10.4 kB
"use strict"; // Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved. // Node module: @loopback/rest // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT var MiddlewareSequence_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.MiddlewareSequence = exports.RestMiddlewareGroups = exports.DefaultSequence = void 0; const tslib_1 = require("tslib"); const core_1 = require("@loopback/core"); const express_1 = require("@loopback/express"); const debug_1 = tslib_1.__importDefault(require("debug")); const keys_1 = require("./keys"); const debug = (0, debug_1.default)('loopback:rest:sequence'); const SequenceActions = keys_1.RestBindings.SequenceActions; /** * The default implementation of SequenceHandler. * * @remarks * This class implements default Sequence for the LoopBack framework. * Default sequence is used if user hasn't defined their own Sequence * for their application. * * Sequence constructor() and run() methods are invoked from [[http-handler]] * when the API request comes in. User defines APIs in their Application * Controller class. * * @example * User can bind their own Sequence to app as shown below * ```ts * app.bind(CoreBindings.SEQUENCE).toClass(MySequence); * ``` */ let DefaultSequence = class DefaultSequence { /** * Constructor: Injects findRoute, invokeMethod & logError * methods as promises. * * @param findRoute - Finds the appropriate controller method, * spec and args for invocation (injected via SequenceActions.FIND_ROUTE). * @param parseParams - The parameter parsing function (injected * via SequenceActions.PARSE_PARAMS). * @param invoke - Invokes the method specified by the route * (injected via SequenceActions.INVOKE_METHOD). * @param send - The action to merge the invoke result with the response * (injected via SequenceActions.SEND) * @param reject - The action to take if the invoke returns a rejected * promise result (injected via SequenceActions.REJECT). */ constructor(findRoute, parseParams, invoke, send, reject) { this.findRoute = findRoute; this.parseParams = parseParams; this.invoke = invoke; this.send = send; this.reject = reject; /** * Optional invoker for registered middleware in a chain. * To be injected via SequenceActions.INVOKE_MIDDLEWARE. */ this.invokeMiddleware = () => false; } /** * Runs the default sequence. Given a handler context (request and response), * running the sequence will produce a response or an error. * * Default sequence executes these steps * - Executes middleware for CORS, OpenAPI spec endpoints * - Finds the appropriate controller method, swagger spec * and args for invocation * - Parses HTTP request to get API argument list * - Invokes the API which is defined in the Application Controller * - Writes the result from API into the HTTP response * - Error is caught and logged using 'logError' if any of the above steps * in the sequence fails with an error. * * @param context - The request context: HTTP request and response objects, * per-request IoC container and more. */ async handle(context) { try { const { request, response } = context; // Invoke registered Express middleware const finished = await this.invokeMiddleware(context); if (finished) { // The response been produced by the middleware chain return; } const route = this.findRoute(request); const args = await this.parseParams(request, route); const result = await this.invoke(route, args); debug('%s result -', route.describe(), result); this.send(response, result); } catch (error) { this.reject(context, error); } } }; exports.DefaultSequence = DefaultSequence; tslib_1.__decorate([ (0, core_1.inject)(SequenceActions.INVOKE_MIDDLEWARE, { optional: true }), tslib_1.__metadata("design:type", Function) ], DefaultSequence.prototype, "invokeMiddleware", void 0); exports.DefaultSequence = DefaultSequence = tslib_1.__decorate([ tslib_1.__param(0, (0, core_1.inject)(SequenceActions.FIND_ROUTE)), tslib_1.__param(1, (0, core_1.inject)(SequenceActions.PARSE_PARAMS)), tslib_1.__param(2, (0, core_1.inject)(SequenceActions.INVOKE_METHOD)), tslib_1.__param(3, (0, core_1.inject)(SequenceActions.SEND)), tslib_1.__param(4, (0, core_1.inject)(SequenceActions.REJECT)), tslib_1.__metadata("design:paramtypes", [Function, Function, Function, Function, Function]) ], DefaultSequence); /** * Built-in middleware groups for the REST sequence */ var RestMiddlewareGroups; (function (RestMiddlewareGroups) { /** * Invoke downstream middleware to get the result or catch errors so that it * can produce the http response */ RestMiddlewareGroups.SEND_RESPONSE = 'sendResponse'; /** * Enforce CORS */ RestMiddlewareGroups.CORS = express_1.MiddlewareGroups.CORS; /** * Server OpenAPI specs */ RestMiddlewareGroups.API_SPEC = express_1.MiddlewareGroups.API_SPEC; /** * Default middleware group */ RestMiddlewareGroups.MIDDLEWARE = express_1.MiddlewareGroups.MIDDLEWARE; RestMiddlewareGroups.DEFAULT = RestMiddlewareGroups.MIDDLEWARE; /** * Find the route that can serve the request */ RestMiddlewareGroups.FIND_ROUTE = 'findRoute'; /** * Perform authentication */ RestMiddlewareGroups.AUTHENTICATION = 'authentication'; /** * Parse the http request to extract parameter values for the operation */ RestMiddlewareGroups.PARSE_PARAMS = 'parseParams'; /** * Invoke the target controller method or handler function */ RestMiddlewareGroups.INVOKE_METHOD = 'invokeMethod'; })(RestMiddlewareGroups || (exports.RestMiddlewareGroups = RestMiddlewareGroups = {})); /** * A sequence implementation using middleware chains */ let MiddlewareSequence = MiddlewareSequence_1 = class MiddlewareSequence { /** * Constructor: Injects `InvokeMiddleware` and `InvokeMiddlewareOptions` * * @param invokeMiddleware - invoker for registered middleware in a chain. * To be injected via RestBindings.INVOKE_MIDDLEWARE_SERVICE. */ constructor(context, invokeMiddleware, options = MiddlewareSequence_1.defaultOptions) { this.invokeMiddleware = invokeMiddleware; this.options = options; this.middlewareView = new express_1.MiddlewareView(context, options); debug('Discovered middleware', this.middlewareView.middlewareBindingKeys); } /** * Runs the default sequence. Given a handler context (request and response), * running the sequence will produce a response or an error. * * Default sequence executes these groups of middleware: * * - `cors`: Enforces `CORS` * - `openApiSpec`: Serves OpenAPI specs * - `findRoute`: Finds the appropriate controller method, swagger spec and * args for invocation * - `parseParams`: Parses HTTP request to get API argument list * - `invokeMethod`: Invokes the API which is defined in the Application * controller method * * In front of the groups above, we have a special middleware called * `sendResponse`, which first invokes downstream middleware to get a result * and handles the result or error respectively. * * - Writes the result from API into the HTTP response (if the HTTP response * has not been produced yet by the middleware chain. * - Catches error logs it using 'logError' if any of the above steps * in the sequence fails with an error. * * @param context - The request context: HTTP request and response objects, * per-request IoC container and more. */ async handle(context) { debug('Invoking middleware chain %s with groups %s', this.options.chain, this.options.orderedGroups); const options = { middlewareList: this.middlewareView.middlewareBindingKeys, validate: MiddlewareSequence_1.defaultOptions.validate, ...this.options, }; await this.invokeMiddleware(context, options); } }; exports.MiddlewareSequence = MiddlewareSequence; MiddlewareSequence.defaultOptions = { chain: keys_1.RestTags.REST_MIDDLEWARE_CHAIN, orderedGroups: [ // Please note that middleware is cascading. The `sendResponse` is // added first to invoke downstream middleware to get the result or // catch errors so that it can produce the http response. RestMiddlewareGroups.SEND_RESPONSE, RestMiddlewareGroups.CORS, RestMiddlewareGroups.API_SPEC, RestMiddlewareGroups.MIDDLEWARE, RestMiddlewareGroups.FIND_ROUTE, // authentication depends on the route RestMiddlewareGroups.AUTHENTICATION, RestMiddlewareGroups.PARSE_PARAMS, RestMiddlewareGroups.INVOKE_METHOD, ], /** * Reports an error if there are middleware groups are unreachable as they * are ordered after the `invokeMethod` group. */ validate: groups => { const index = groups.indexOf(RestMiddlewareGroups.INVOKE_METHOD); if (index !== -1) { const unreachableGroups = groups.slice(index + 1); if (unreachableGroups.length > 0) { throw new Error(`Middleware groups "${unreachableGroups.join(',')}" are not invoked as they are ordered after "${RestMiddlewareGroups.INVOKE_METHOD}"`); } } }, }; exports.MiddlewareSequence = MiddlewareSequence = MiddlewareSequence_1 = tslib_1.__decorate([ (0, core_1.injectable)({ scope: core_1.BindingScope.SINGLETON }), tslib_1.__param(0, core_1.inject.context()), tslib_1.__param(1, (0, core_1.inject)(keys_1.RestBindings.INVOKE_MIDDLEWARE_SERVICE)), tslib_1.__param(2, (0, core_1.config)()), tslib_1.__metadata("design:paramtypes", [core_1.Context, Function, Object]) ], MiddlewareSequence); //# sourceMappingURL=sequence.js.map