注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

snoopyxdy的博客

https://github.com/DoubleSpout

 
 
 

日志

 
 

expressjs源码解读(五) —— 3.0版本route模块(2)  

2011-12-22 17:17:57|  分类: node |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
最后一章了,写的太乱只供自己留个纪念,代码比较长,我直接把注释写在代码中了,先上调度员的代码:

Router.prototype._dispatch = function(req, res, next){
  var params = this.params
    , self = this;
//定义一些变量

  // route dispatch,添加一个闭包函数pass,用来做递归
  (function pass(i, err){
    var paramCallbacks
      , paramIndex = 0
      , paramVal
      , route
      , keys
      , key
      , ret;

    // match next route,匹配下route.keys数组的下一项
    function nextRoute(err) {
      pass(req._route_index + 1, err);//这个req._route_index是self._match函数生成的
    }

    // match route,将req请求和i参数传递给_match函数进行匹配,最后返回route对象
    req.route = route = self._match(req, i);

    // implied OPTIONS,如果_match未匹配成功,并且是options请求的,则执行_options方法返回支持的http methods 数组
    if (!route && 'OPTIONS' == req.method) return self._options(req, res);

    // no route,还记得next吗?在connect.js中next代表匹配下一个中间件,如果未匹配成功则到下一个中间件,可能是404
    if (!route) return next(err);

    // we have a route
    // start at param 0,如果匹配成功,赋值匹配到的参数值和匹配的到参数key
    req.params = route.params;
    keys = route.keys; //这里的keys是期望传递给这个route的参数数组
    i = 0;

    // param callbacks,定义参数的回调函数
    function param(err) {
      paramIndex = 0;
      key = keys[i++];
      paramVal = key && req.params[key.name];
      paramCallbacks = key && params[key.name]; 
//这里还记得吗?我们可以利用param(key, paramCallbacks )来注册方法,当接收到这个参数时执行此回调函数

      try {
        if ('route' == err) {
          nextRoute();
        } else if (err) {
          i = 0;
          callbacks(err); //以上是如果出现错误则分别根据错误执行不同的东西
        } else if (paramCallbacks && undefined !== paramVal) {
          paramCallback();//如果定义了回调函数并且参数的值存在,则执行paramCallback函数
        } else if (key) {//否则如果key存在,则递归执行param
          param();
        } else {//如果key不存在,则把i设置为0,执行callbacks函数
          i = 0;
          callbacks();
        }
      } catch (err) {
        param(err);//当出现任何错误,递归调用自己,其实就实行var i=0, callbacks(err)
      }
    };

    param(err);
    
    // single param callbacks
    function paramCallback(err) {
      var fn = paramCallbacks[paramIndex++]; //将paramCallbacks数组中的回调第paramIndex个取出来
      if (err || !fn) return param(err); //如果出现错误,则执行param(err),相当于执行callbacks函数
      fn(req, res, paramCallback, paramVal, key.name); //否则执行fn回调函数内的内容
    }

    // invoke route callbacks
    function callbacks(err) {//上面出现错误时,会调用callbacks这个函数
      var fn = route.callbacks[i++];//将route对象中的callbacks数组出列第i个
      try {
        if ('route' == err) {
          nextRoute();
        } else if (err && fn) {
          if (fn.length < 4) return callbacks(err);//如果有err传参,并且回调函数的期望参数少于4时,则跳过此回调继续下一个
          fn(err, req, res, callbacks); //否则执行回调函数,将err,req,res,callbacks作为参数传递给回调函数
        } else if (fn) {
          fn(req, res, callbacks);
//如果没有错误,并且存在回调函数,则执行回调函数,这里的回调函数是在客户端定义的,比如:
//app.get('/', function(req, res, next){
// do something;
//      next()
//})
        } else {
          nextRoute(err);//否则执行下一个
        }
      } catch (err) {
        callbacks(err); //当有错误时,执行下一个回调函数
      }
    }


  })(0);
//pass函数尾部,从i=0,开始遍历route.keys数组
};


紧接着我们来看_match方法:

Router.prototype._match = function(req, i, head){ //比对方法,这里期望传递3个参数,req对象,i,和head
  var method = req.method.toLowerCase()
    , url = parse(req.url)
    , path = url.pathname
    , routes = this.map
    , captures
    , route
    , keys;

  // HEAD support
  // TODO: clean this up
  if (!head && 'head' == method) {//如果head参数没有传递,但是请求方式head时
    // attempt lookup
    route = this._match(req, i, true);//递归调用自己,将head参数传递为true,返回route对象
    if (route) return route; //如果head请求递归调用后route存在,则直接返回route对象

    // default to GET as res.render() / res.send()
    // etc support HEAD
     method = 'get'; //如果递归调用head请求递归调用以后route不存在,则当成get请求来继续
  }

  // routes for this method
  if (routes = routes[method]) { //如果map注册了这个method方法,例如get,post,则继续,否则返回undefined,注意这里作者坑爹了,将routes复制成了self.map[method]的数组了,我个人比较反感同一个变量名代表几个完全不同概念的变量。


    // matching routes,循环每个method方法中的path是否和req请求的path比对
    for (var len = routes.length; i < len; ++i) {
      route = routes[i]; //这里的routes表示self.map[method]的数组
      if (captures = route.match(path)) {
        keys = route.keys; //期望获取的参数名数组
        route.params = []; //新建一个map[metod]params数组

        // params from capture groups
        for (var j = 1, jlen = captures.length; j < jlen; ++j) {//captures是正则exec匹配生成的数组
          var key = keys[j-1]
            , val = 'string' == typeof captures[j] //如果是字符,则对uricode解码然后复制给val
              ? decodeURIComponent(captures[j])
              : captures[j];
          if (key) {
            route.params[key.name] = val; //如果key为真,即存在期望传递参数的key,则将val值放入params对象中
          } else {
            route.params.push(val); 
          }
        }

        // all done
        req._route_index = i; //保存比对成功时的map[method]数组的下标i
        return route;
      }
    }

  }

};

看了这么多代码头都晕了,里面实施的细节比之前要清晰不少,大体的思路如下:
1、例如客户端get请求了一个地址,并且带上了一些参数
2、expressjs执行_dispatch方法调度器,带上i=0;其实这里的i是给 _match 方法用的,太坑爹了,既然给_match用就不要往pass方法里传i了。
3、调度器根据请求的方法和请求地址以及所带的参数去_match匹配
3-1、如果匹配失败并且请求方式不是options则执行下一个中间件,跳出_dispatch方法,跳出route中间件模块。
3-2、如果匹配成功,则将匹配成功的信息返回给_dispatch调度器,然后执行参数传入的函数和访问时注册的path的回调函数。当然如果这里定义了多个此类的回调,作者利用递归代替循环来逐个调用(就是next(),这里的next()和中间件的next()不同哦)。感觉这里也是作者比较装B的地方。
4、上述回调执行完成以后,由于之前记录了self.map[method]数组的匹配下标,所以继续从此下标开始查找,如果再次找到匹配此方法和path的情况,则重复3-2,如果未找到匹配结果则跳转到3-1,跳出route中间件模块。
  评论这张
 
阅读(1571)| 评论(1)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016