接上篇初探
上篇写的很乱,只是个人临时决定写的。主要试图从app.use和app.[method]之间区别着手,来解释express的中间件和路由的实现逻辑。
本篇
本篇很有可能依然保持上篇的发散性思维,所以各位读者可能要小心阅读,避免吐槽而亡。
环境
保持与上篇一致,源码版本是express.js 4.14.1
request源码
打开request.js,从上往下看代码
IncomingMessage
var req = exports = module.exports = {
__proto__: http.IncomingMessage.prototype
};
代码具体戳这里
我们要导出request这个对象,跟http.IncomingMessage有什么关系,而且是挂在request的__proto__原型链上
我们知道nodejs提供的原生http对象通常是这么用的
var http = require('http');
http.createServer( (function(req, res) {
res.end("hello world");
}) ).listen("3000");
那么我们看看nodejs的源代码里面的http.js,找到下面的代码
const server = require('_http_server');
...
const Server = exports.Server = server.Server;
exports.createServer = function(requestListener) {
return new Server(requestListener);
};
上面可以看到是回调函数是从_http_server.js里面出来的,那么继续翻_http_server.js的代码,如下
function Server(requestListener) {
...
if (requestListener) {
this.addListener('request', requestListener);
}
...
}
可以看到是观察者模式,那么可以_http_server.js里搜索关键字 "emit('request'",迅速找到这里的触发函数
if (req.headers.expect !== undefined &&
(req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
...
self.emit('request', req, res);
...
} else {
...
self.emit('request', req, res);
...
}
可以看到这个req是从这里传递下去的,下面看到380行
function onParserExecuteCommon(ret, d) {
...
var req = parser.incoming;
...
}
当然还有好多处可以看见var req = parser.incoming;那么去看看incoming怎么来的,去翻_http_commong.js 60行
parser.incoming = new IncomingMessage(parser.socket);
那么可以知道,其实request就是构造器IncomingMessage new 出来的一个对象
这里也就解释了我们express的req为什么要在原型链上加上IncomingMessage
req.get && req.header
这一部分,就是返回http头的字段,比较有意思的就是referer
那个规范http作者当年给拼错了referer,所以导致现在referrer与referer表达的是同一个意思
switch (lc) {
case 'referer':
case 'referrer':
return this.headers.referrer
|| this.headers.referer;
default:
return this.headers[lc];
}
req.accepts
这个是个accepts库,主要用于验证http的资源类型
如果匹配返回true;相反则返回undefined,这时候response应该返回406
还有其他的几个accepts,不一一介绍了
req.range
下载的http头中可以携带range字段,指明想要下载资源的哪一部分
Range: bytes=500-999
如果资源总的大小是10000,而且正常返回http 200,那么res的头里面应该是这样的
Content-Range: bytes 500-999/10000
那req.range这个函数本身是干什么的呢?
req.range = function range(size, options) {
var range = this.get('Range');
if (!range) return;
return parseRange(size, range, options);
};
翻译一下注解及翻阅源码:
- 如果req的http头中Range字段未给出,该函数返回undefined
- 如果这个资源不能划分成size范围内的,该函数返回-1
- 如果语法错误,该函数返回-2
- 正常的返回,如下例
Range: bytes=500-600,601-999 //http头Range字段
//那么该函数返回
[
{"start" : 500, "end" : 600},
{"start" : 601, "end" : 999}
]
//并且返回的数组对象有一个属性type = bytes, 这里值为bytes是因为请求头Range中里面的"bytes"
req.param
这个比较简单, param(name, defaultValue)就是在req.params、req.body、req.query等对象里面取这个name的属性值,如果都找不到,那就返回defaultValue
req.params、body、query这三个分别指解析了如下部分
- 路由的占位: /user/:id
- body里面的数据: id=12, {"id":12}
- url里面的参数: ?id=12
req.is
就是判断请求的http数据类型,直接拿函数注释例子就好
// With Content-Type: text/html; charset
req.is('html');
req.is('text/html');
req.is('text/*');
// => true
// When Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true
req.is('html');
// => false
defineGetter
剩下的函数都是get访问器
- protocol //http或者https
- secure //返回true或者false,安全就是https协议
- ip //返回ip地址
- ips //ip地址,包括中间代理的ip
- subdomains //子域名数组,tobi.ferrets.example.com返回 ["ferrets", "tobi"]
- path //url里面的路径
- hostname //主机名
- host //上面一样,后面要被废弃
- fresh // 根据Last-Modified和ETag判断客户端是否是最新缓存,没有的话顺手缓存一下
- stale //同上,但是判断的是客户端缓存是否过期
- xhr //判断是否是xmlhttprequest(ajax)发送的请求
就是利用defineProperty添加get访问器,当尝试访问属性的时候会触发,由于这些不可写,所以没有set访问器
结合
那么nodejs http.createServer里面的参数req对象,怎么就和现在我们定义的req对象结合上去了呢?
看中间件/lib/middleware/init.js
exports.init = function(app){
return function expressInit(req, res, next){
...
//通过原型链,赋给req对象的
req.__proto__ = app.request;
res.__proto__ = app.response;
...
};
};
未经同意,禁止转载
by chainhelen