nodejs express 框架解密4-路由

本文档是基于express3.4.6

express 的路由是自己去实现的,没有使用connect中的路由中间件模块。

1、在如何创建一个app那篇中,我们提到了路由,

  //router
  //路由
  this._router = new Router(this);
  this.routes = this._router.map;
  this.__defineGetter__('router', function(){
    this._usedRouter = true;
    this._router.caseSensitive = this.enabled('case sensitive routing');
    this._router.strict = this.enabled('strict routing');
    return this._router.middleware;
  });

可以看到,是将Router这个类存储到了app上的_rounter这个属性上去了,然后将所有得路由映射存储到了routes 这个属性上了。最后在给app定义一个router属性,直接调用了这个模块的

middleware模块。我们来看看Router类

function Router(options) {
  options = options || {};
  var self = this;
  this.map = {};
  this.params = {};
  this._params = [];
  this.caseSensitive = options.caseSensitive;
  this.strict = options.strict;
  this.middleware = function router(req, res, next){
    self._dispatch(req, res, next);
  };
}

从上面的代码可以看出,middleware调用了自身的_dispatch 函数 ,这个函数的作用就是路由分发

Router.prototype._dispatch = function(req, res, next){
  var params = this.params
    , self = this;

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

  // route dispatch
  (function pass(i, err){
    var paramCallbacks
      , paramIndex = 0
      , paramVal
      , route
      , keys
      , key;

    // match next route
    function nextRoute(err) {
      pass(req._route_index + 1, err);
    }

    // match route
    req.route = route = self.matchRequest(req, i);
    // implied OPTIONS
    if (!route && 'OPTIONS' == req.method) return self._options(req, res);

    // no route
    if (!route) return next(err);
    debug('matched %s %s', route.method, route.path);

    // we have a route
    // start at param 0
    req.params = route.params;
    keys = route.keys;
    i = 0;

    // param callbacks
    function param(err) {
      paramIndex = 0;
      key = keys[i++];
      paramVal = key && req.params[key.name];
      paramCallbacks = key && params[key.name];

      try {
        if ('route' == err) {
          nextRoute();
        } else if (err) {
          i = 0;
          callbacks(err);
        } else if (paramCallbacks && undefined !== paramVal) {
          paramCallback();
        } else if (key) {
          param();
        } else {
          i = 0;
          callbacks();
        }
      } catch (err) {
        param(err);
      }
    };

    param(err);

    // single param callbacks
    function paramCallback(err) {
      var fn = paramCallbacks[paramIndex++];
      if (err || !fn) return param(err);
      fn(req, res, paramCallback, paramVal, key.name);
    }

    // invoke route callbacks
    function callbacks(err) {
      var fn = route.callbacks[i++];

      try {
        if ('route' == err) {
          nextRoute();
        } else if (err && fn) {
          if (fn.length < 4) return callbacks(err);
          fn(err, req, res, callbacks);
        } else if (fn) {
          if (fn.length < 4) return fn(req, res, callbacks);
          callbacks();
        } else {
          nextRoute(err);
        }
      } catch (err) {
        callbacks(err);
      }
    }
  })(0);
};

这个函数是通过pass 这个自动执行的函数进行路由转发的,

首先通过请求, req.route = route = self.matchRequest(req, i); 来配置路由,返回需要的信息

可以看到matchRequest 函数返回了(我访问了下http://localhost:3000)

{ path: '/',
  method: 'get',
  callbacks: [ [Function] ],
  keys: [],
  regexp: /^\/\/?$/i,
  params: []
 }

看看 matchRequest 这个函数

Router.prototype.matchRequest = function(req, i, head){
  var method = req.method.toLowerCase()
    , url = parse(req)
    , path = url.pathname
    , routes = this.map
    , i = i || 0
    , route;

  // HEAD support
  if (!head && 'head' == method) {
    route = this.matchRequest(req, i, true);
    if (route) return route;
     method = 'get';
  }

  // routes for this method
  if (routes = routes[method]) {

    // matching routes
    for (var len = routes.length; i < len; ++i) {
      route = routes[i];
      if (route.match(path)) {
        req._route_index = i;
        return route;
      }
    }
  }
};

它返回一个路由的处理结果。

后面根据参数执行了param() 函数。这个函数主要是处理callback回调函数的。

2.给路由注册各种函数:

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

    // deprecated
    if (Array.isArray(path)) {
      console.trace('passing an array to app.VERB() is deprecated and will be removed in 4.0');
    }

    // if no router attached yet, attach the router
    if (!this._usedRouter) this.use(this.router);

    // setup route
    console.log(method,'test2');
    this._router[method].apply(this._router, arguments);
    return this;
  };
});

这个函数直接添加了开始的注册函数,下面的这个methods.foreach 依次为每一个app.get,app.post 等等 添加路由和 callback 函数

Router.prototype.route = function(method, path, callbacks){
  var method = method.toLowerCase()
    , callbacks = utils.flatten([].slice.call(arguments, 2));

  // ensure path was given
  if (!path) throw new Error('Router#' + method + '() requires a path');

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

  // create the route
  debug('defined %s %s', method, path);
  var route = new Route(method, path, callbacks, {
    sensitive: this.caseSensitive,
    strict: this.strict
  });

  // add it
  (this.map[method] = this.map[method] || []).push(route);
  return this;
};

Router.prototype.all = function(path) {
  var self = this;
  var args = [].slice.call(arguments);
  methods.forEach(function(method){
      self.route.apply(self, [method].concat(args));
  });
  return this;
};

methods.forEach(function(method){
  Router.prototype[method] = function(path){
    var args = [method].concat([].slice.call(arguments));
    this.route.apply(this, args);
    return this;
  };
});

最后一个函数,直接执行,给路由添加get,post等函数。

在Router.prototype.route 函数中,我们调用了一个:

var route = new Route(method, path, callbacks, {
    sensitive: this.caseSensitive,
    strict: this.strict
  });

Route 是一个路由的基本单元,包含2个方法:

function Route(method, path, callbacks, options) {
  options = options || {};
  this.path = path;
  this.method = method;
  this.callbacks = callbacks;
  this.regexp = utils.pathRegexp(path
    , this.keys = []
    , options.sensitive
    , options.strict);
}

/**
 * Check if this route matches `path`, if so
 * populate `.params`.
 *
 * @param {String} path
 * @return {Boolean}
 * @api private
 */

Route.prototype.match = function(path){
  var keys = this.keys
    , params = this.params = []
    , m = this.regexp.exec(path);

  if (!m) return false;

  for (var i = 1, len = m.length; i < len; ++i) {
    var key = keys[i - 1];

    var val = 'string' == typeof m[i]
      ? utils.decode(m[i])
      : m[i];

    if (key) {
      params[key.name] = val;
    } else {
      params.push(val);
    }
  }

  return true;
};