@loopback/rest
Version:
Expose controllers as REST endpoints and route REST API requests to controller methods
242 lines • 10.4 kB
JavaScript
// 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
;