express源码阅读(1)——路由结构

2018-02-05

由于最近2个面试都提到了express,需要了解中间件怎么运作,内部原理是什么,所以特意去看了express源码,最后出来了这篇文章。


源码是当前最新版本,version:4.16.2
我们平时项目引入express的源文件index.js,

module.exports = require('./lib/express');

知道lib/express.js为主文件
我们先回顾下怎么用express启用服务器的

var app = require('express')();
var server = http.createServer(app);
server.listen(port);

了解的人应该知道这个http方法,http.createServer([requestListener]),
requestListener 是一个函数,会被自动添加到 'request' 事件

了解这些后,再来看看index.js的代码

// index.js
var bodyParser = require('body-parser')   // 解析body参数的
var EventEmitter = require('events').EventEmitter;  // 事件监听对象
var mixin = require('merge-descriptors');   // 合并对象
var proto = require('./application');     // app的大部分方法
var Route = require('./router/route');
var Router = require('./router'); 
var req = require('./request');
var res = require('./response');

exports = module.exports = createApplication;

function createApplication() {
  // 这个app就是返回的 requestListener 函数, 用于http.createServer([requestListener])
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);  // 把EventEmitter的原型方法合并到app
  mixin(app, proto, false);                   // 把proto的方法方法合并到app

  // expose the prototype that will get set on requests
  app.request = Object.create(req, {  // 在指定原型对象上添加新属性后的对象。
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  app.init(); // 初始化,在proto(application.js)里面
  return app;
}

主要把一些方法都集成在app当中,再看看看app.init()

// application.js
var app = exports = module.exports = {};

var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default';

app.init = function init() {
  this.cache = {};
  this.engines = {};
  this.settings = {};

  this.defaultConfiguration();
};

app.defaultConfiguration = function defaultConfiguration() {
  var env = process.env.NODE_ENV || 'development';

  // default settings
  this.enable('x-powered-by');
  this.set('etag', 'weak');
  this.set('env', env);
  this.set('query parser', 'extended');
  this.set('subdomain offset', 2);
  this.set('trust proxy', false);

  // trust proxy inherit back-compat
  Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
    configurable: true,
    value: true
  });

  debug('booting in %s mode', env);

  this.on('mount', function onmount(parent) {
    // inherit trust proxy
    if (this.settings[trustProxyDefaultSymbol] === true
      && typeof parent.settings['trust proxy fn'] === 'function') {
      delete this.settings['trust proxy'];
      delete this.settings['trust proxy fn'];
    }

    // inherit protos
    setPrototypeOf(this.request, parent.request)
    setPrototypeOf(this.response, parent.response)
    setPrototypeOf(this.engines, parent.engines)
    setPrototypeOf(this.settings, parent.settings)
  });

  // setup locals
  this.locals = Object.create(null);

  // top-most app is mounted at /
  this.mountpath = '/';

  // default locals
  this.locals.settings = this.settings;

  // default configuration
  this.set('view', View);
  this.set('views', resolve('views'));
  this.set('jsonp callback name', 'callback');

  if (env === 'production') {
    this.enable('view cache');
  }
};

这部分主要设置了一些初始化的东西

在使用当中,我们主要使用的app.use中间件、app.getapp.post等路由方法。
现在我们来看看这两个问题express怎么生成路由中间件是怎么运作的

1. express怎么生成路由

查到了一些app没有get,post之类的方法,但从application.js可以找到这段代码

// application.js
methods.forEach(function(method){
  app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }

    this.lazyrouter();

    var route = this._router.route(path);
    route[method].apply(route, slice.call(arguments, 1));  
    return this;
  };
});

methos是一个引入的库var methods = require('methods');,查看github上的methods源码知道它就是一个请求方法的数组['get, 'post', ...], 所以这段代码就是注册了这些app.get, app.post等方法

route[method].apply(route, slice.call(arguments, 1));
可以知道我们注册的路由function就是Array.prototype.slice.call(arguments, 1);

我们主要看看route到底是什么
this.lazyrouter(); 可以找到this._router的值,就是一个router实例。

app.lazyrouter = function lazyrouter() {
  if (!this._router) {
    this._router = new Router({
      caseSensitive: this.enabled('case sensitive routing'),
      strict: this.enabled('strict routing')
    });

    this._router.use(query(this.get('query parser fn')));
    this._router.use(middleware.init(this));
  }
};

Router是导入的lib/router/index.js

var proto = module.exports = function(options) {
  var opts = options || {};

  function router(req, res, next) {
    router.handle(req, res, next);
  }

  // mixin Router class functions
  setPrototypeOf(router, proto);   // 把proto作为router的原型

  router.params = {};
  router._params = [];
  router.caseSensitive = opts.caseSensitive;
  router.mergeParams = opts.mergeParams;
  router.strict = opts.strict;
  router.stack = [];

  return router;
};

setPrototypeOf(router, proto); 这句话把proto作为router的原型
我们来看看proto有没有route这个方法,答案是有的

proto.route = function route(path) {
  var route = new Route(path);

 // 一个layer层实例,存着route,方便以后回调方法
  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));

  layer.route = route;

  this.stack.push(layer);   // router的stack数组里添加layer, 以后匹配路由遍历使用
  return route;
};

创建了Route实例和Layer实例;
把route作为属性引入在layer实例中,并在stack加入layer实例(这里主要是当路由触发,遍历的时候用到,以后再讲)
所以之前的route也就是一个Route实例。
我们再看看/lib/router/route.js

// /lib/router/route.js
module.exports = Route;

function Route(path) {
  this.path = path;
  this.stack = [];

  debug('new %o', path)

  // route handlers for various http methods
  this.methods = {};
}

methods.forEach(function(method){
  Route.prototype[method] = function(){
    var handles = flatten(slice.call(arguments));

    for (var i = 0; i < handles.length; i++) {
      var handle = handles[i];

      if (typeof handle !== 'function') {
        var type = toString.call(handle);
        var msg = 'Route.' + method + '() requires a callback function but got a ' + type
        throw new Error(msg);
      }

      debug('%s %o', method, this.path)

      var layer = Layer('/', {}, handle);
      layer.method = method;

      this.methods[method] = true;
      this.stack.push(layer);
    }

    return this;
  };
});

route实例也会存在一个stack, 主要用于一个路由可能有多个回调函数。
app.post('/', authFn, cb)
所以之前的这段代码route[method].apply(route, slice.call(arguments, 1));
其实就是执行这个原型方法。
handles就是我们路由的回调函数。
每个Route都会创建一个Layer实例,并在layer层存储在route的stack里面。
我们再看看Layer构造函数

function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  debug('new %o', path)
  var opts = options || {};

  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // set fast path flags
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

所以可以知道,layer实例存储着我们的handle方法,也就是我们路由的回调函数。

现在我们总结下express的大体结构,大概是这样的:

express大体结构

今天就到这里吧。下次再写写中间件app.use