.5-浅析express源码之Router模块(1)-默认中间件
模块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)-默认中间件的更多相关文章
- .7-浅析express源码之Router模块(3)-app[METHODS]
之前的讨论都局限于use方法,所有方式的请求会被通过,这一节讨论express内部如何处理特殊请求方法. 给个流程图咯~ 分别给出app.METHODS与router.METHODS: // app. ...
- .6-浅析express源码之Router模块(2)-router.use
这一节继续深入Router模块,首先从最常用的use开始. router.use 方法源码如下: proto.use = function use(fn) { var offset = 0; var ...
- express源码剖析--Router模块
1.加载模块执行代码: methods.forEach(function(method){ //method是http协议的各种请求方法,如:get,put,headee,post Route.pro ...
- .2-浅析express源码之applicaiton模块(1)-咸鱼方法
上一节讲了express的入口文件,当执行主函数,会调用app.init方法,这个方法就来源于application模块. 这个模块有很多方法,目前仅仅过一下初始化方法: app.init = fun ...
- .3-浅析express源码之applicaiton模块(2)-app.render
这个模块还漏了一个稍微复杂点的API,就是app.render,首先看官网的定义: app.render(view, [locals], callback) view为对应的文件名,locals为一个 ...
- .4-浅析express源码之applicaiton模块(3)-compile函数
基本上application模块的api都看的差不多了,但是在app.set中还有一个遗漏点,如下: app.set = function set(setting, val) { // ...设值 / ...
- express源码分析之Router
express作为nodejs平台下非常流行的web框架,相信大家都对其已经很熟悉了,对于express的使用这里不再多说,如有需要可以移步到www.expressjs.com自行查看express的 ...
- nginx源码分析之模块初始化
在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...
- 读Zepto源码之Event模块
Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...
随机推荐
- 右键在目录当前打开命令行cmd窗口
Win7系统大家习惯“Win+R”的组合键打开命令提示符. 方法/步骤2 通常情况下,我们点击鼠标右键是没有命令行选项的. 方法/步骤3 在桌面上先按住Shift键,然后鼠标右键,出现选项“在此处打开 ...
- Kali Linux渗透测试实战 1.1 Kali Linux简介
1.1 Kali Linux简介 如果您之前使用过或者了解BackTrack系列Linux的话,那么我只需要简单的说,Kali是BackTrack的升级换代产品,从Kali开始,BackTrack将成 ...
- ConcurrentHashMap源码解析(3)
此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.get(Object key) 使用方法: map.get("hello"); 源代 ...
- 3、JUC--ConcurrentHashMap 锁分段机制
ConcurrentHashMap Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能. ConcurrentHashMap 同步容器 ...
- is和 == 的区别以及编码.解码
一.is和==的区别 1,id( ) id( )是python的一个内置函数,通过id( )我们可以查看到一个变量表的值在内存中的地址: s = 2 print(id(s)) # 1514368064 ...
- Codeforces Round #479 (Div. 3)解题报告
题目链接: http://codeforces.com/contest/977 A. Wrong Subtraction 题意 给定一个数x,求n次操作输出.操作规则:10的倍数则除10,否则减1 直 ...
- 跟着刚哥学习Spring框架--JDBC(六)
Spring的JDBC框架 Spring JDBC提供了一套JDBC抽象框架,用于简化JDBC开发. Spring主要提供JDBC模板方式.关系数据库对象化方式.SimpleJdbc方式.事务管理来简 ...
- Python-flask跨站请求伪造和跨站请求保护的实现
图中 Browse 是浏览器,WebServerA 是受信任网站/被攻击网站 A,WebServerB 是恶意网站/点击网站 B. (1) 一开始用户打开浏览器,访问受信任网站 A,输入用户名和密码登 ...
- python粘包分析与解决
TCP与UDP协议 TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务器端)都要有一一成对的socket,因此 ...
- 57.storm拓扑结构调整
几个概念 Topology(拓扑):Spout.Bolt组成的一个完整的流程结构: Stream Grouping:流分组.数据的分发方式: Spout:直译 水龙头,也就是 消息源 的意思: Bol ...