Express源码分析

Express源码分析

1. 引入并调用express()时发生了什么?

  1. 使用const express = require("express")时, 默认会去寻找express模块下面的index.js文件
  2. index.js中, 我们发现:
module.exports = require('./lib/express');
  1. 即引用了lib目录下面的express.js 文件
  2. express.js中, 首行: exports = module.exports = createApplication, 即默认导出了createApplication这个函数
  3. 再看一下createApplication :
function createApplication() {
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // 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();
  return app;
}
  1. createApplication中返回了一个Express示例, 即我们调用express()时, 等价于createApplication()

2. app.listen(port, host, callback) 执行时, Express底层做了哪些事情?

  • 在创建Express实例时, 我们发现app的实例上只挂载了request和response两个对象, 并没有listen方法, 其实是通过mixin(app, proto, false) 挂载的, proto是一个对象, 在application.js中定义的
  • application.js找到listen方法, 源码如下:
app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};
  • 原来是调用了NodeJS原生的http模块, 创建了一个server, 并开启了监听,http.createServer中的this指向app, 等价于如下:
app.listen = function listen() {
  var server = http.createServer(function(req, res, next) {
    app.handle(req, res, next);
  });
  return server.listen.apply(server, arguments);
};

3. 当使用中间件 app.use(middleware) 时Express做了什么事情?

  • app.use = function use(fn), fn的形式是一个函数
  • function中会调用 this.lazyrouter(); , 来初始化路由, 源码如下:
//application.js
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));
  }
};

 var fns = flatten(slice.call(arguments, offset));

  if (fns.length === 0) {
    throw new TypeError('app.use() requires a middleware function')
  }
this.lazyrouter();
var router = this._router;

  fns.forEach(function (fn) {
    // non-express app
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn);
    }

    debug('.use app under %s', path);
    fn.mountpath = path;
    fn.parent = this;

    // restore .app property on req and res
    router.use(path, function mounted_app(req, res, next) {
      var orig = req.app;
      fn.handle(req, res, function (err) {
        setPrototypeOf(req, orig.request)
        setPrototypeOf(res, orig.response)
        next(err);
      });
    });

    // mounted an app
    fn.emit('mount', this);
  }, this);
  • 原来, 调用use使用中间件时, Express会创建一个router实例,然后获取中间件数组, 遍历所有的中间件匹配和当前参数相同的那个中间件, 最后通过router去处理, 因此, 更深一步的细节在router.use()这个方法里面
  • router.use的源码如下: 在router/index.js下面
proto.use = function use(fn) {
  var offset = 0;
  var path = '/';

  // default path to '/'
  // disambiguate router.use([fn])
  if (typeof fn !== 'function') {
    var arg = fn;

    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0];
    }

    // first arg is the path
    if (typeof arg !== 'function') {
      offset = 1;
      path = fn;
    }
  }

  var callbacks = flatten(slice.call(arguments, offset));

  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires a middleware function')
  }

  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];

    if (typeof fn !== 'function') {
      throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
    }

    // add the middleware
    debug('use %o %s', path, fn.name || '<anonymous>')

    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);

    layer.route = undefined;

    this.stack.push(layer);
  }

  return this;
};
  • 可以看到:
    1. 首先判断了参数
    2. 拿到use中所有注册的回调函数
    3. 遍历所有回调, 如果有不是函数类型的参数则抛出错误,并把每一个回调函数变成一个layer对象
    4. 把layer放入栈中

4. 用户发送了http请求, 注册的中间件如何被执行?

  • 由于启动了服务, 所有的请求都会被监听到, 因此会执行监听时的回调:
var server = http.createServer(function(req, res, next) {
    app.handle(req, res, next);
  });
  • 来看app.handle这个方法: 其实就是初始化了一个router, 并执行了router.handle()
app.handle = function handle(req, res, callback) {
    var router = this._router;

    // final handler
    var done = callback || finalhandler(req, res, {
        env: this.get('env'),
        onerror: logerror.bind(this)
    });

    // no routes
    if (!router) {
        debug('no routes defined on app');
        done();
        return;
    }

    router.handle(req, res, done);
};
  • 在看router.handle()方法: 简单来看, 就是初始化了一些参数, 并取出全局的stack, 调用next()
var self = this;

  debug('dispatching %s %s', req.method, req.url);

  var idx = 0;
  var protohost = getProtohost(req.url) || ''
  var removed = '';
  var slashAdded = false;
  var paramcalled = {};

  // store options for OPTIONS request
  // only used if OPTIONS request
  var options = [];

  // middleware and routes
  var stack = self.stack;

  // manage inter-router variables
  var parentParams = req.params;
  var parentUrl = req.baseUrl || '';
  var done = restore(out, req, 'baseUrl', 'next', 'params');

  // setup next layer
  req.next = next;

  // for options requests, respond with a default if nothing else responds
  if (req.method === 'OPTIONS') {
    done = wrap(done, function(old, err) {
      if (err || options.length === 0) return old(err);
      sendOptionsResponse(res, options, old);
    });
  }

  // setup basic req values
  req.baseUrl = parentUrl;
  req.originalUrl = req.originalUrl || req.url;

  next();
  • 因此, 需要看一下next的具体实现:
function next(err) {
    var layerError = err === 'route'
      ? null
      : err;

    // remove added slash
    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }

    // restore altered req.url
    if (removed.length !== 0) {
      req.baseUrl = parentUrl;
      req.url = protohost + removed + req.url.substr(protohost.length);
      removed = '';
    }

    // signal to exit router
    if (layerError === 'router') {
      setImmediate(done, null)
      return
    }

    // no more matching layers
    if (idx >= stack.length) {
      setImmediate(done, layerError);
      return;
    }

    // get pathname of request
    var path = getPathname(req);

    if (path == null) {
      return done(layerError);
    }

    // find next matching layer
    var layer;
    var match;
    var route;

    while (match !== true && idx < stack.length) {
      layer = stack[idx++];
      match = matchLayer(layer, path);
      route = layer.route;

      if (typeof match !== 'boolean') {
        // hold on to layerError
        layerError = layerError || match;
      }

      if (match !== true) {
        continue;
      }

      if (!route) {
        // process non-route handlers normally
        continue;
      }

      if (layerError) {
        // routes do not match with a pending error
        match = false;
        continue;
      }

      var method = req.method;
      var has_method = route._handles_method(method);

      // build up automatic options response
      if (!has_method && method === 'OPTIONS') {
        appendMethods(options, route._options());
      }

      // don't even bother matching route
      if (!has_method && method !== 'HEAD') {
        match = false;
        continue;
      }
    }

    // no match
    if (match !== true) {
      return done(layerError);
    }

    // store route for dispatch on change
    if (route) {
      req.route = route;
    }

    // Capture one-time layer values
    req.params = self.mergeParams
      ? mergeParams(layer.params, parentParams)
      : layer.params;
    var layerPath = layer.path;

    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (err) {
        return next(layerError || err);
      }

      if (route) {
        return layer.handle_request(req, res, next);
      }

      trim_prefix(layer, layerError, layerPath, path);
    });
  }
  • 其实next()无非就是做了一下几件事情

    1. 之前用户在注册中间件时, 需要执行的回调以layer对象的形式存储在了全局的一个stack中,因此在响应时, 需要找到与路径匹配的layer
    2. 找到对应的layer之后, 调用layer.handle_request(req, res, next), 最终由layer处理响应
  • 最终响应的执行: layer.handle_request() ,源码如下:

Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle;

  if (fn.length > 3) {
    // not a standard request handler
    return next();
  }

  try {
    fn(req, res, next);
  } catch (err) {
    next(err);
  }
};
  • 在该方法中:
    1. 判断handle参数的个数
    2. 如果参数没问题, 就执行用户传进来的回调函数
    3. 若出现错误, 则调用执行错误的中间

5. next()为什么能调用下一个中间件?

  • stack中的idx++, 会再去stack中寻找下一个匹配的中间件调用!
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值