node_modules/egg-core/lib/loader/mixin/middleware.js

'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;
}