app.route
和app.param
是express4.2新增的两个api,这里做一些说明:
express的官方文档时这样解释的:
Returns an instance of a single route which can then be used to handle HTTP verbs with optional middleware. Using app.route() is a recommended approach to avoiding duplicate route naming and thus typo errors.
返回一个route实例,其实就是express源码里的router/index.js
里定义的组件,作用是保存一个路由路径的信息及其响应函数,使用app.route
可以让你不用重复配置一条路径对应的不同http请求的路由。
比如:
app.get('/example',function(){}); app.post('/example',function(){}); app.put('/example',function(){});
就可以简写成:app.route("/example").get(function(){}).post(function(){}).put(function(){})
.
这样有多少好处,就见仁见智了。
app.route
的作用就是对同一路径下,有不同http请求响应处理时,代码书写更简便了。
官方给了一个这样的例子,/events
路径所有类型的http请求都将在这个路由配置里响应
var app = express();
app.route('/events')
.all(function(req, res, next) {
// runs for all HTTP verbs first
// think of it as route specific middleware!
})
.get(function(req, res, next) {
res.json(...);
})
.post(function(req, res, next) {
// maybe add a new event...
})
我们先来看一下源码:
app.lazyrouter = function() {
if (!this._router) {
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
this._router.use(query());
this._router.use(middleware.init(this));
}
};
app.route = function(path){
this.lazyrouter();
return this._router.route(path);
};
首先调用this.lazyrouter(), 生成两个中间件query
,expressInit
并插入,不知道的看本博客另一篇文章,然后调用router组件的route函数,生成一个路由中间件放入中间件栈中。下面详细看看route()
的代码:
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.route = route;
this.stack.push(layer);
return route;
};
很简单,就是用一个layer
封装一个route
中间件放入栈中。将遵循express的触发流程,和app.get
,app.use
不冲突。
###1.如何使用 这个api的作用是捕获路径中的通配符参数,取个例子:
app.param('id', /^\d+$/);
app.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
app.param('range', /^(\w+)\.\.(\w+)?$/);
app.get('/range/:range', function(req, res){
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
});
路径中通配符:
经常是隐含的request参数,app.param()
通过正则表达式非常方便的将其取下,放入req.params
中。
app.param
也可以不用正则表达式,直接匹配所有类型数据,并建立一个中间件处理:
app.param('user', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
next(err);
} else if (user) {
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
这样所有符合的路径都会进入中间件,注意到中间件新增了一个参数,说明其和中间件不同
别急还有一种用法:
app.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures;
next();
} else {
next('route');
}
}
}
});
app.param
匹配了所有通配符,回调函数中name是通配名称,fn是一个正则表达式对象,这样我们可以想到第一种使用方法里也可以写成这样:
app.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
if(name=="user"&&fn.match(val))
res.send('user ' + val);
else
next()//继续执行其他param
}
}
});
###2. 源码分析 下面看看源代码:
app.param = function(name, fn){
var self = this;
self.lazyrouter();
if (Array.isArray(name)) {
name.forEach(function(key) {
self.param(key, fn);
});
return this;
}
self._router.param(name, fn);
return this;
};
可以看到通配名称name可以指定多个,一一处理,主要的执行在router组件里面:
proto.param = function(name, fn){
// param logic,这是第三种用法,只传入一个function
if ('function' == typeof name) {
this._params.push(name);
return;
}
// apply param functions
var params = this._params;
var len = params.length;
var ret;
if (name[0] === ':') {
name = name.substr(1);
}
// 第三种用法len==0, 获取param中间件,fn也可能为一个正则表达式
// 或一个function(rq, res),
// 就是`app.param(function(name,fn){ return ... })`
// 返回的`function(req, res, next, val){}`
for (var i = 0; i < len; ++i) {
if (ret = params[i](name, fn)) {
fn = ret;
}
}
// ensure we end up with a
// middleware function
if ('function' != typeof fn) {
throw new Error('invalid param() call for ' + name + ', got ' + fn);
}
//将param中间件加入param栈中,第一种用法,和第二种用法
(this.params[name] = this.params[name] || []).push(fn);
return this;
};
下面看看param是怎么触发执行的吧,我们所有的param,形式为function(req, res, next, val){}
,都储存在router.params
中,触发执行更中间件一样在router.handle()
中,在router递归调用next()时触发:
function next(err) {
if (err === 'route') {
err = undefined;
}
//取出中间件
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
req.url = protohost + removed + req.url.substr(protohost.length);
req.originalUrl = req.originalUrl || req.url;
removed = '';
try {
var path = parseUrl(req).pathname;
if (undefined == path) path = '/';
if (!layer.match(path)) return next(err);
// route object and not middleware
var route = layer.route;
// if final route, then we support options
if (route) {
// we don't run any routes with error first
if (err) {
return next(err);
}
req.route = route;
// we can now dispatch to the route
if (method === 'options' && !route.methods['options']) {
options.push.apply(options, route._options());
}
}
req.params = layer.params;
// this should be done for the layer,处理params,params处理完才会执行回调`function(err)`,执行中间件。
return self.process_params(layer, req, res, function(err) {
if (err) {
return next(err);
}
//执行中间件,中间件需要自己调用next形成递归调用
if (route) {
return layer.handle(req, res, next);
}
trim_prefix();
});
} catch (err) {
next(err);
}
self.process_params(layer, req, res, function(err) {})
是代码里的关键,他的回调函数中执行了中间件,说明param处理永远在中间件前,下面看看这个函数:
proto.process_params = function(route, req, res, done) {
var params = this.params;
// captured parameters from the route, keys and values
// route保存的通配值,这里route = new layer();
var keys = route.keys;
// fast track
if (!keys || keys.length === 0) {
return done();
}
var i = 0;
var paramIndex = 0;
var key;
var paramVal;
var paramCallbacks;
// process params in order
// param callbacks can be async
function param(err) {
if (err) {
return done(err);
}
if (i >= keys.length ) {
return done();
}
paramIndex = 0;
key = keys[i++];
paramVal = key && req.params[key.name];
paramCallbacks = key && params[key.name];
try {
if (paramCallbacks && undefined !== paramVal) {
return paramCallback();
} else if (key) {
return param();
}
} catch (err) {
return done(err);
}
//执行回调,继续中间件的next链条
done();
}
// single param callbacks
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
}
param();
};
处理方式跟中间件差不多,不过这里隐藏了两个递归,需要仔细看看:
一个是
param()
,遍历一遍params
,取出所有param处理 一个是paramCallback
,遍历param里的每个fn回调函数并执行,paramCallback实现递归,需要在app.param
的回调函数中调用next()
。
function paramCallback(err) {
var fn = paramCallbacks[paramIndex++];
if (err || !fn) return param(err);
fn(req, res, paramCallback, paramVal, key.name);
}
fn的形式为funtion(req, res, next, val, key)
,现在知道app.param
的回调应该怎么用了吧。
###keys和params的来源 处理param时,一直出现两个数组keys和params,这里给予解答:keys在layer.js中生成,
this.regexp = pathRegexp(path, this.keys = [], options);
在layer的构造函数中使用pathRegexp模块获取路径中的通配符的值,放入keys数组中。
在调用app.param
的时候:
(this.params[name] = this.params[name] || []).push(fn);
params[name] 会有一个数组保存他的回调函数,也就是说,一个通配会有多个回调,依次执行。
Written with StackEdit.
mark