'use strict'; const join = require('path').join; const is = require('is-type-of'); const inspect = require('util').inspect; const assert = require('assert'); const debug = require('debug')('egg-core:middleware'); const pathMatching = require('egg-path-matching'); const utils = require('../../utils'); module.exports = { /** * Load app/middleware * * app.config.xx is the options of the middleware xx that has same name as config * * @function EggLoader#loadMiddleware * @param {Object} opt - LoaderOptions * @example * ```js * // app/middleware/status.js * module.exports = function(options, app) { * // options == app.config.status * return function*(next) { * yield next; * } * } * ``` * @since 1.0.0 */ loadMiddleware(opt) { this.timing.start('Load Middleware'); const app = this.app; // load middleware to app.middleware opt = Object.assign({ call: false, override: true, caseStyle: 'lower', directory: this.getLoadUnits().map(unit => join(unit.path, 'app/middleware')), }, opt); const middlewarePaths = opt.directory; this.loadToApp(middlewarePaths, 'middlewares', opt); for (const name in app.middlewares) { Object.defineProperty(app.middleware, name, { get() { return app.middlewares[name]; }, enumerable: false, configurable: false, }); } this.options.logger.info('Use coreMiddleware order: %j', this.config.coreMiddleware); this.options.logger.info('Use appMiddleware order: %j', this.config.appMiddleware); // use middleware ordered by app.config.coreMiddleware and app.config.appMiddleware const middlewareNames = this.config.coreMiddleware.concat(this.config.appMiddleware); debug('middlewareNames: %j', middlewareNames); const middlewaresMap = new Map(); for (const name of middlewareNames) { if (!app.middlewares[name]) { throw new TypeError(`Middleware ${name} not found`); } if (middlewaresMap.has(name)) { throw new TypeError(`Middleware ${name} redefined`); } middlewaresMap.set(name, true); const options = this.config[name] || {}; let mw = app.middlewares[name]; mw = mw(options, app); assert(is.function(mw), `Middleware ${name} must be a function, but actual is ${inspect(mw)}`); mw._name = name; // middlewares support options.enable, options.ignore and options.match mw = wrapMiddleware(mw, options); if (mw) { if (debug.enabled) { // show mw debug log on every request mw = debugWrapper(mw); } app.use(mw); debug('Use middleware: %s with options: %j', name, options); this.options.logger.info('[egg:loader] Use middleware: %s', name); } else { this.options.logger.info('[egg:loader] Disable middleware: %s', name); } } this.options.logger.info('[egg:loader] Loaded middleware from %j', middlewarePaths); this.timing.end('Load Middleware'); }, }; function wrapMiddleware(mw, options) { // support options.enable if (options.enable === false) return null; // support generator function mw = utils.middleware(mw); // support options.match and options.ignore if (!options.match && !options.ignore) return mw; const match = pathMatching(options); const fn = (ctx, next) => { if (!match(ctx)) return next(); return mw(ctx, next); }; fn._name = mw._name + 'middlewareWrapper'; return fn; } function debugWrapper(mw) { const fn = (ctx, next) => { debug('[%s %s] enter middleware: %s', ctx.method, ctx.url, mw._name); return mw(ctx, next); }; fn._name = mw._name + 'DebugWrapper'; return fn; }