Skip to content
This repository has been archived by the owner on Oct 31, 2022. It is now read-only.

Latest commit

 

History

History
262 lines (201 loc) · 6.39 KB

File metadata and controls

262 lines (201 loc) · 6.39 KB

route.js

1. 模块依赖

/**
 * Module dependencies.
 */

var debug = require('debug')('express:router:route');
var Layer = require('./layer');
var methods = require('methods');
var utils = require('../utils');

2. 导出对象

该模块的导出对象即为Route类,源码如下:

/**
 * Expose `Route`.
 */

module.exports = Route;

/**
 * Initialize `Route` with the given `path`,
 *
 * @param {String} path
 * @api private
 */

function Route(path) {
  debug('new %s', path);
  this.path = path;
  this.stack = [];

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

一个Router实例主要有如下属性:

  • path
  • stack
  • methods

3. Route.prototype.all

该方法为所有的HTTP请求方法添加一条路由,源码如下:

/**
 * Add a handler for all HTTP verbs to this route.
 *
 * Behaves just like middleware and can respond or call `next`
 * to continue processing.
 *
 * You can use multiple `.all` call to add multiple handlers.
 *
 *   function check_something(req, res, next){
 *     next();
 *   };
 *
 *   function validate_user(req, res, next){
 *     next();
 *   };
 *
 *   route
 *   .all(validate_user)
 *   .all(check_something)
 *   .get(function(req, res, next){
 *     res.send('hello world');
 *   });
 *
 * @param {function} handler
 * @return {Route} for chaining
 * @api public
 */

Route.prototype.all = function(){
  var callbacks = utils.flatten([].slice.call(arguments));
  callbacks.forEach(function(fn) {
    if (typeof fn !== 'function') {
      var type = {}.toString.call(fn);
      var msg = 'Route.all() requires callback functions but got a ' + type;
      throw new Error(msg);
    }

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

    this.methods._all = true;
    this.stack.push(layer);
  }, this);

  return this;
};

该方法主要思路:获取所有参数并进行扁平化处理。对于每个参数,如果不是函数,则报错,如果是函数,则构造一个Layer对象,并将构造的Layer对象添加到this.stack数组中。同时将this.methods._all设置为true

4. VERB方法

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

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

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

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

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

该方法与Route.prototype.all方法非常类似。首先methods是一个依赖模块,其导出对象就是一个数组,包括所有的HTTP方法。因此这段代码就是为Route.prototype添加getpost等方法,且整体逻辑与Route.prototype.all基本类似,不同点为:

  • layer.method设置为method,而不是undefined
  • this.methods中,设置相关的method属性为true

5. Route.prototype._handles_method

该方法用来判断该路由能否处理给定的HTTP请求方法,源码如下:

Route.prototype._handles_method = function _handles_method(method) {
  if (this.methods._all) {
    return true;
  }

  method = method.toLowerCase();

  if (method === 'head' && !this.methods['head']) {
    method = 'get';
  }

  return Boolean(this.methods[method]);
};

如果this.methods._alltrue,则可以处理一切HTTP方法,因此直接返回true

如果methodheadthis.methods中未对head方法进行设置,则改变method的值为get,即判断是否可以处理GET方法。

最后,判断this.methods中是否设置了相应的HTTP方法,并返回。

6. Route.prototype._options

该方法返回该路由所支持的所有的HTTP请求方法,源码如下:

/**
 * @return {Array} supported HTTP methods
 * @api private
 */

Route.prototype._options = function _options() {
  var methods = Object.keys(this.methods);

  // append automatic head
  if (this.methods.get && !this.methods.head) {
    methods.push('head');
  }

  for (var i = 0; i < methods.length; i++) {
    // make upper case
    methods[i] = methods[i].toUpperCase();
  }

  return methods;
};

这里需要注意的是,如果有GET而没有HEAD方法,则把HEAD方法也加进去。

7. Route.prototype.dispatch

该方法主要是对HTTP请求进行处理分发,源码如下:

/**
 * dispatch req, res into this route
 *
 * @api private
 */

Route.prototype.dispatch = function(req, res, done){
  var idx = 0;
  var stack = this.stack;
  if (stack.length === 0) {
    return done();
  }

  var method = req.method.toLowerCase();
  if (method === 'head' && !this.methods['head']) {
    method = 'get';
  }

  req.route = this;

  next();

  function next(err) {
    if (err && err === 'route') {
      return done();
    }

    var layer = stack[idx++];
    if (!layer) {
      return done(err);
    }

    if (layer.method && layer.method !== method) {
      return next(err);
    }

    if (err) {
      layer.handle_error(err, req, res, next);
    } else {
      layer.handle_request(req, res, next);
    }
  }
};

首先定义了两个变量,idx表示当前下标,stack表示该路由的处理函数列表。然后在进行了一些基本的处理后,会调用next()next函数的思路如下:

  • 如果有错误,则直接调用done()
  • 获取layer,如果layer不存在,则调用done
  • 如果layer.method与当前的HTTP请求方法不匹配,则跳过,执行next(),去获取下一个处理函数
  • 如果layer.method与当前的HTTP请求方法匹配,则根据有无错误选择执行layer.handle_errorlayer.handle_request

例如下面的例子:

app.route('/users')
  .post(function foo(req, res, next) {
    // do something
    next();
  }).get(function bar(req, res, next) {
    // do something
    next();
  });

当通过GET方法访问/users的时候,会先判断handle属性为foo的Layer,发现HTTP请求方法不匹配,因此跳过。然后判断handle属性为bar的Layer,发现匹配,于是调用该Layer的handle_request方法,从而调用bar()