Have the courage to follow your heart and intuition. They somehow already know what you truly want to become. Everything else is secondary
今天来了解整个接收和处理请求的过程。
上一章说了当接收到一个请求的时候是通过这个函数 app.handle(req, res, next);
来处理请求的。今天来说一下具体一个请求的处理过程。
收到请求会执行app.handle(req, res, next)
,具体代码如下:
//application.js
app.handle = function(req, res, done) {
var router = this._router;
// final handler
done = done || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
这个函数接收三个参数req,res,next。 其中next就是上面的done, 默认是finalhandler。具体可以看这里。然后调用router.handle()开始链式调用。
router.handler 代码比较长,先看结构:
// router/index.js
proto.handle = function(req, res, done) {
...... //定义一些基本变量
next();
function next(err) {
......
}
function trim_prefix(layer, layerError, layerPath, path) {
...... //对路径做些处理再调用layer.handle_request
}
};
其中通过递归调用next()来遍历stack(存放中间件的栈,是一个数组)看是否有匹配的中间件。在proto.handle的开头就定义了idx=0,表示当前中间件索引。在next()中首先判断当前中间件索引是否大于stack.length。 如果是说明已遍历完,直接执行done()。
// function next()
if (idx >= stack.length) {
setImmediate(done, layerError);
return;
}
否则的话执行while循环找到第一个可以处理http请求的中间件。 来看一下while的主要代码:
// function next()
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path); //调用了layer.match 方法
route = layer.route;
if (typeof match !== 'boolean') {
layerError = layerError || match;
}
if (match !== true) {
continue; // 不匹配,退出本次循环。
}
if (!route) {
continue;
}
if (layerError) {
match = false;
continue;
}
var method = req.method;
var has_method = route._handles_method(method);
if (!has_method && method === 'OPTIONS') {
appendMethods(options, route._options());
}
if (!has_method && method !== 'HEAD') {
match = false;
continue;
}
}
通过matchLayer确定当前layer是否与当前路径匹配。不匹配则直接结束本次循环进入下一个中间件的判断。match为true但是layer.route不存在,说明不是路由中间件,结束本次循环,由于此时match为true会结束while循环直接进入下面的步骤(这个稍后说)。 如果route存在,即是路由中间件,再判断该路由可处理的http方法是否与请求的http方法一致。如果不一致则根据其method是否为options或head做相应处理,一致则不再对match做修改,这样执行下次while循环的时候由于match为true会结束循环进入下面的步骤。
// function next() 结束while循环后的部分
if (match !== true) {
//结束循环后match仍然为false, 说明是遍历完stack, 没有匹配的中间件
return done(layerError);
}
if (route) {
req.route = route;
}
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
if (route) {
//next作为回调函数传入handle_request,形成一个闭包,再在handle_request中调用next()的时候idx的值仍然是上一次退出while循环的值
return layer.handle_request(req, res, next);
}
//对路径做些处理再调用layer.handle_request
trim_prefix(layer, layerError, layerPath, path);
});
这部分首先进行一些参数预处理,如果为路由中间件就直接调用layer.handle_request,否则先对路径做些处理再调用layer.handle_request。下面是layer.handle_request的代码:
// router/layer.js
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
layer.handle_request会在Router.handle和route.dispatch中调用。注意若是从Router.handle调用的,next是Router.handle里定义的next,若是从route.dispatch调用的,next是dispatch里定义的next。 不同的next遍历的对象有所不同。举个🌰:
//app.js
var user = require('user');
var app = express();
app.use('/index', function(){
//do something
})
app.use('/user', user);
//user.js
var userRouter = require('express').Router();
var checkAuth = function(req, res, next){
//... check auth
next()
};
var getList = function(req, res, next) {
//... get user list
}
userRouter.get('/list', checkAuth, getList);
module.exports = userRouter;
app.js中的两个app.use其实是调用router.use创建了两个layer对象并将其添加到app._router.stack中,以下简称Router。Router.stack应该是类似这样的[ layer1, layer2 ]
。 其中layer2实际上是express.Router()的导出对象, 在app.use方法中会视为一个子app封装一层。像这样:
//application.js app.use()
function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function (err) {
req.__proto__ = orig.request;
res.__proto__ = orig.response;
next(err);
});
这里的fn实际上就是app.use('/user',user)
中的user(即user.js中导出的Router)。完整代码可查看application.js 的app.use。
上面user.js中调用userRouter.get方法创建一个新的route和layer(这里暂时叫newLayer, 这个layer会被push到userRouter.stack中), 再调用route.get方法把checkAuth和getList添加到route.stack中,所以该route的stack应该类似这样 [ layer3, layer4 ]
,userRouter.stack是这样的[ newLayer ]
。 这里附上Router.route和 Router.VERB的代码。
// router/index.js
proto.route = function(path){
var route = new Route(path);
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route)); //layer.handle=route.dispatch
layer.route = route;
this.stack.push(layer);
return route;
};
// 创建 Router#VERB functions
methods.concat('all').forEach(function(method){
proto[method] = function(path){
// 调用proto.route new 一个route对象。
var route = this.route(path)
//在route.VERB或route.all中会把传入的fn添加到该route的stack中
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
所以当收到一个http请求(eg. http://host.com/user/list), 首先执行app.handle(), 在 [layer1, layer2]
中查找匹配,当查找的layer2的时候,路径匹配且layer2不是一个路由中间件,进入到layer.handle_request调用layer.handle(这里的layer.handle就是mounted_app方法),里面又调用fn.handle就是执行userRouter.handle方法,又继续在userRouter.stack([ newLayer ]
)中查找匹配, newLayer路径与请求路径匹配且是路由中间件,进入layer.handle_request执行layer.handle,注意这里的layer.handle是route.dispatch,因为在Router.VERB中创建layer时是new Layer(path, option, route.dispatch.bind(route))
。所以就会在route.stack([ layer3, layer4 ]
)中查找匹配。附上route.dispatch代码:
Route.prototype.dispatch = function(req, res, done){
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}
var method = req.method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = this;
next();
function next(err) {
if (err && err === 'route') {
return done();
}
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
return next(err);
}
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
可以看到这里的idx是0,与Router.handle()中的idx不同,route.dispatch是在当前route.stack中遍历匹配。在上面的🌰中route.stack就是[ layer3, layer4 ]
。处理逻辑与Router.handle类似,idx是当前下表,stack是当前路由处理函数列表,递归调用next。 这里next方法判断当前layer是否存在,不存在或方法不匹配调用done, 否则根据err值执行handle_request或handle_error。例子中的layer3路径和method都匹配,执行layer3.handle_request调用this.handle即checkauth方法,如果通过验证就会执行next(),注意这时的next是dispatch中定义的next方法。此时idx指向layer4, 依然匹配,继续执行getList方法。 getList执行后若不再调用next(), 则处理请求的过程结束, 否则继续执行next, 此时route.stack遍历完再找不到layer会执行done(err) 返回到[newLayer]
层面,userRouter.stack也遍历完(userRouter.handle中的idx此时为1,大于[newLayer].length
)于是又调用done, 回到上层的Router.stack中([layer1, layer2]
)依次类推。当热由于getList处理完请求后直接reponse所以不会再调用next()。 这里只是分析下如果继续调用next的行为。
表述得可能不太清楚,看图: