模块application已经完结,开始讲Router路由部分。

  切入口仍然在application模块中,方法就是那个随处可见的lazyrouter。

  基本上除了初始化init方法,其余的app.use、app.route、app.param等等,所有涉及到路由的方法都会调用一次这个函数,用来初始化一个应用的内部路由。

  而这个内部路由对于每个应用来说是唯一的,可以看下源码:

app.lazyrouter = function lazyrouter() {
if (!this._router) {
// 生成一个实例
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
// params解析中间件调用
this._router.use(query(this.get('query parser fn')));
// express框架自定义的内部中间件
this._router.use(middleware.init(this));
}
};

  很清晰的步骤,在生成一个Router实例后,调用了两个中间件。

  这里有一个问题,为什么不在初始化的函数中直接生成一个默认路由呢?

  原因在于设置路由的相关参数需要调用app.set方法,这个方法明显需要有app实例,如果在获取app实例的时候就初始化了一个路由,这个路由的参数就没办法配置了。因此,在获取app实例后,必须先对路由参数进行配置,然后再调用对应的app.use等方法。

  简单看一眼构造函数:

var proto = module.exports = function(options) {
var opts = options || {};
// 跟app一样的函数
function router(req, res, next) {
router.handle(req, res, next);
}
// 原型方法挂载
setPrototypeOf(router, proto) router.params = {};
router._params = [];
/**
* caseSensitive => 区分大小写 /foo vs /Foo
* mergeParmas => 保留父路由参数
* strict => 严格模式 /foo vs /foo/
*/
router.caseSensitive = opts.caseSensitive;
router.mergeParams = opts.mergeParams;
router.strict = opts.strict;
router.stack = []; return router;
};

  默认情况下,三个参数的值均为undefined,构造函数没有任何初始化的操作,直接返回了router函数。

  接下来是两个中间件。

query

  先把那行代码单独贴出来:

// var query = require('./middleware/query');
this._router.use(query(this.get('query parser fn')));

  前面有讲解3个特殊键的set会触发对应的compile方法设置fn,这里的query parser fn就是之一。

  默认情况下,query parser值为extended,对应的query parser fn为qs.parse方法,因此这里query方法的参数为一个函数。

  看一眼query方法:

module.exports = function query(options) {
// options为函数 merge后opts也是函数
var opts = merge({}, options)
var queryparse = qs.parse;
// 参数修正
if (typeof options === 'function') {
queryparse = options;
opts = undefined;
}
// 兼容 设置配置参数
if (opts !== undefined && opts.allowPrototypes === undefined) opts.allowPrototypes = true;
// 中间件标准结构
return function query(req, res, next) {
if (!req.query) {
var val = parseUrl(req).query;
req.query = queryparse(val, opts);
} next();
};
};

  这里的形参options既可以是配置参数,也可以是预设的解析方法。

  如果将query parser设为false,这里的options就是一个空对象,express还是会指定一个parser,即源码中的qs.parse。搞了半天,设置false或者extended都是默认的qs.parse。

  在确实了对应的parse方法与参数后,就开始进行url解析,先处理url,获取query参数,再解析query设置到req对象上。

parseUrl

  讲这个之前,需要稍微理解下nodejs的url模块,特别是Url与URL。

  这两东西在网上没查到详细的区别,通过试API,发现差别还挺大:

1、Url为遗留API,构造函数不接受参数,通过无参构造后,可以调用parse方法解析一个url路径来获得一个实例,实例属性包含protocol、auth等一系列东西。

2、URL为WHATWG API,推荐使用的新API,可以直接通过new操作传一个url进去获得实例,属性同样包含那些,但是在键名与分类略有区别。

  详细情况可见:http://nodejs.cn/api/url.html#url_url_strings_and_url_objects。

  虽然URL是新东西而且node推荐使用,但是在express源码的这个方法中依然使用的是老Url,入口函数如下:

function parseurl(req) {
// 这个属性是原生的
var url = req.url; if (url === undefined) return undefined;
// 尝试获取缓存属性
var parsed = req._parsedUrl;
// 判断有没有缓存
if (fresh(url, parsed)) return parsed; // 解析url
parsed = fastparse(url);
parsed._raw = url;
// 添加缓存并返回结果
return (req._parsedUrl = parsed)
};

  所有的解析都基于一个原生的属性,即req.url,该属性返回请求的原始URL。

  这里的获取缓存就不看了,比较简单,直接看如何快速解析url路径:

function fastparse(str) {
// 当路径结构为纯path(例如:/path/ext?a=1)时,直接调用node原生的parse方法
if (typeof str !== 'string' || str.charCodeAt(0) !== 0x2f /* / */ ) {
return parse(str)
} var pathname = str
var query = null
var search = null // This takes the regexp from https://github.com/joyent/node/pull/7878
// 这个issue主要讲当url是纯路径时 用node原生的Url.parse会更快
for (var i = 1; i < str.length; i++) {
switch (str.charCodeAt(i)) {
/**
* 遇到问号开始切割路径
* http://www.baidu.com?a=1 =>
* {
* pathname: http://www.baidu.com,
* query: a=1,
* search: ?a=1,
* }
*/
case 0x3f:
/* ? */
if (search === null) {
pathname = str.substring(0, i)
query = str.substring(i + 1)
search = str.substring(i)
}
break
// 遇到其余不合理的情况调用原生方法
case 0x09:
/* \t */
case 0x0a:
/* \n */
case 0x0c:
/* \f */
case 0x0d:
/* \r */
case 0x20:
/* */
case 0x23:
/* # */
case 0xa0:
case 0xfeff:
return parse(str)
}
}
// 生成一个Url对象或者空对象
var url = Url !== undefined ?
new Url() : {};
// 添加对应的属性
url.path = str
url.href = str
url.pathname = pathname
url.query = query
url.search = search return url
}

  看似很长,实则很简单。简单来说,就是根据问号来切割url,特殊情况就全部扔给内置模块解析,最后返回url对象。

  在获取到对应的url尾部参数后,调用parser方法解析生成一个参数对象挂载到req上,所以在实际应用中,我们可以直接调用req.query来得到请求参数值。

middleware.init

  这个中间件是express自定义的,也不知道叫什么,所以直接用调用名作为小标题了。

  源码如下:

exports.init = function(app) {
return function expressInit(req, res, next) {
// 这玩意儿默认生效的
if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express');
// 属性各种挂载
req.res = res;
res.req = req;
req.next = next;
// 本地模块原型设置
setPrototypeOf(req, app.request)
setPrototypeOf(res, app.response) res.locals = res.locals || Object.create(null); next();
};
};

  这个中间件的主要作用就是把内置模块的属性、方法全部加到原生的req、res上面去,后面就能使用express的方法了。

  解析完毕。

.5-浅析express源码之Router模块(1)-默认中间件的更多相关文章

  1. .7-浅析express源码之Router模块(3)-app[METHODS]

    之前的讨论都局限于use方法,所有方式的请求会被通过,这一节讨论express内部如何处理特殊请求方法. 给个流程图咯~ 分别给出app.METHODS与router.METHODS: // app. ...

  2. .6-浅析express源码之Router模块(2)-router.use

    这一节继续深入Router模块,首先从最常用的use开始. router.use 方法源码如下: proto.use = function use(fn) { var offset = 0; var ...

  3. express源码剖析--Router模块

    1.加载模块执行代码: methods.forEach(function(method){ //method是http协议的各种请求方法,如:get,put,headee,post Route.pro ...

  4. .2-浅析express源码之applicaiton模块(1)-咸鱼方法

    上一节讲了express的入口文件,当执行主函数,会调用app.init方法,这个方法就来源于application模块. 这个模块有很多方法,目前仅仅过一下初始化方法: app.init = fun ...

  5. .3-浅析express源码之applicaiton模块(2)-app.render

    这个模块还漏了一个稍微复杂点的API,就是app.render,首先看官网的定义: app.render(view, [locals], callback) view为对应的文件名,locals为一个 ...

  6. .4-浅析express源码之applicaiton模块(3)-compile函数

    基本上application模块的api都看的差不多了,但是在app.set中还有一个遗漏点,如下: app.set = function set(setting, val) { // ...设值 / ...

  7. express源码分析之Router

    express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于express的使用这里不再多说,如有需要可以移步到www.expressjs.com自行查看express的 ...

  8. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  9. 读Zepto源码之Event模块

    Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...

随机推荐

  1. Swift3 倒计时按钮扩展

    extension UIButton{ func overrideSelf(){ self.setTitle("验证码", for: .normal) self.titleLabe ...

  2. [leetcode 14]Longest Common Prfix

    1 题目: Write a function to find the longest common prefix string amongst an array of strings. Hide Ta ...

  3. SQLSERVER CXPACKET 等待

    --SQLSERVER CXPACKET 等待 2013-6-11 2 --联机丛书: 3 --当尝试同步查询处理器交换迭代器时出现.如果针对该等待类型的争用成为问题时,可以考虑降低并行度 4 5 6 ...

  4. Node.js之绝对选择(2018版)

    [这篇是很早期的文字,由于引用较广泛,担心误导,故按照现在的情形做一些修改] 几年前,完全放弃Asp.net,彻底脱离微软方向.Web开发,在公司团队中,一概使用Node.js.Mongodb.Git ...

  5. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  6. JQuery Mobile - 处理图片加载失败!

    重点来了:一定要记住error事件不冒泡(如果要用js的方法替换默认出错图片,记得把img的alt属性去掉). 相关的知识点:jquery的ready方法.$("img").err ...

  7. Linux的1000个命令

    目录 Linux常用命令 uptime wget uname free who last history pwd cd ls cat head tail tr wc cut diff touch mk ...

  8. skynet 源码阅读笔记 bootstrap.lua

    最近几周粗略看了 skynet 代码的 C 部分.遇到很多知识点以前只是知道,但并不十分了解,所以这是一个学习的过程. 从 main 函数开始,闷头一阵看下来,着实蛋疼. 当看了 skynet_mq. ...

  9. hashMap tableSizeFor 实现原理

    基于jdk1.8 hashMap实现,要求容量大小是2的整次方,例如:2/4/8/16/32/64/128...,而不能是中间的某个值.这是为什么呢? map是数组+链表的数据结构,读写数据都需要首先 ...

  10. C#6.0语言规范(十九) 文档注释

    C#为程序员提供了一种机制,可以使用包含XML文本的特殊注释语法来记录他们的代码.在源代码文件中,具有特定形式的注释可用于指示工具从这些注释和它们之前的源代码元素生成XML.使用这种语法的注释称为文档 ...