路由用于确定应用程序如何响应对特定端点的客户机请求,包含一个 URI(或路径)和一个特定的 HTTP 请求方法(GET、POST 等)。
每个路由可以具有一个或多个处理程序函数,这些函数在路由匹配时执行。它的使用形式如下:
app.METHOD(PATH, HANDLER)
其中:
app 是 express 的实例。
METHOD 是 HTTP 请求方法。
PATH 是服务器上的路径。
HANDLER 是在路由匹配时执行的函数。
我们先来演示一下路由的简单使用:
router.get('/', function(req, res, next) {
res.send('receive get method:'+ req.url);
});
router.post('/', function(req, res, next) {
res.send('receive post method:'+ req.url);
});
上面演示的get,post都属于路由方法,它们是派生自 HTTP 方法之一,附加到 express 类的实例。
Express 支持对应于 HTTP 方法的以下路由方法:get、post、put、head、delete、options、trace、copy、lock、mkcol、move、purge、propfind、proppatch、unlock、report、mkactivity、checkout、merge、m-search、notify、subscribe、unsubscribe、patch、search 和 connect。
也就是说上面的使用其实是对node.js中http模块的封装。但是有一个方法例外:app.all(),该方法并非派生自 HTTP 方法,它用于在所有请求方法的路径中装入中间件函数。
说直白一点就是使用这个路由方法我们可以不用管发来的是get还是post请求,我都可以处理。
使用示例:
app.all('/secret', function (req, res, next) {
// do some things
next(); // 将控制全交给后续路由
});
我们常见的路由是准确的路径,比如:’/’,’/login’等,但是还有一些特殊的路由,它们可以是字符串模式或者正则表达式。
基于字符串模式的路由路径
此路由路径将匹配 acd 和 abcd。
app.get('/ab?cd', function(req, res) {
res.send('ab?cd');
});
基于正则表达式的路由路径:
此路由路径将匹配名称中具有“a”的所有路由。
app.get(/a/, function(req, res) {
res.send('/a/');
});
我们可以提供多个回调函数,以类似于中间件的行为方式来处理请求。唯一例外是这些回调函数可能调用 next(‘route’) 来绕过剩余的路由回调。可以使用此机制对路由施加先决条件,在没有理由继续执行当前路由的情况下,可将控制权传递给后续路由。
路由处理程序的形式可以是一个函数、一组函数或者两者的结合,如以下示例中所示。
多个回调函数可以处理一个路由:
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
});
大家注意一点如果有多个路由处理程序,那么不是最后一个路由的情况下,其参数要有next:
function (req, res, next)
接下来我们要提的是中间件函数的概念,中间件函数能够访问请求对象 (req)、响应对象 (res) 以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为 next 的变量来表示。
中间件函数可以执行以下任务:
执行任何代码。
对请求和响应对象进行更改。
结束请求/响应循环。
调用堆栈中的下一个中间件。
如果当前中间件函数没有结束请求/响应循环,那么它必须调用 next(),以将控制权传递给下一个中间件函数。否则,请求将保持挂起状态。
下面是一张来自官方文档的示例:
next() 函数不是 Node.js 或 Express API 的一部分,而是传递给中间件函数的第三自变量。next() 函数可以命名为任何名称,但是按约定,始终命名为“next”。为了避免混淆,请始终使用此约定。
next函数主要负责将控制权交给下一个中间件,如果当前中间件没有终结请求,并且next没有被调用,那么请求将被挂起,后边定义的中间件将得不到被执行的机会。
这里还有一点需要注意:next('route')
它表示 将控制权传递给下一个路由,那么next()函数表示的应该就是讲控制权传递给下一个路由处理程序了吧。我们看个例子:
app.use('/', function(req,res,next){
console.log('func 1');
},function(req,res,next){
console.log('func 2');
},function(req,res){
console.log('func 3')
});
假设我们有这么一个路由,那么按照我们预想的应该是依次输出:
func 1 func 2 func 3
我们先来运行一下:
并且此时你访问浏览器,你会看到:
是不是很奇怪,在官方文档中有这样的描述:
您可以提供多个回调函数,以类似于中间件的行为方式来处理请求。
唯一例外是这些回调函数可能调用 next('route') 来绕过剩余的路由回调。
’您可以使用此机制对路由施加先决条件,在没有理由继续执行当前路由的情况下,可将控制权传递给后续路由。
下表中响应对象 (res) 的方法可以向客户机发送响应,并终止请求/响应循环。如果没有从路由处理程序调用其中任何方法,客户机请求将保持挂起状态。
那么,我们分析一下我们的代码,在这段代码中,我们只是打印了一句log,并没有对客户机进行回应,也就是说此时客户机请求 是挂起状态,通俗点就是你女朋友说快下楼了,然后你就一直在楼下等。
那么next()的作用显而易见,就是执行后边的回调函数,我们在试试:
app.use('/', function(req,res,next){
console.log('func 1');
next()
},function(req,res,next){
console.log('func 2');
next()
},function(req,res){
console.log('func 3')
res.send("hello world")
});
好了,next()方法是个什么意思想必大家都明白了。接下来再看看next('route')
:
app.get('/',function(req,res,next){
console.log('func 1');
next('route');
},function(req,res,next){
console.log('func 2');
next();
});
app.use('/', function(req,res){
console.log('func 4');
res.send("hello world2");
});
运行结果:
ok,当执行了next('route')
后,func2 回调函数被跳过,然后执行下一个路由。这样大家想必有了一个比较直观的认识了。
特别注意:next('route') 仅在使用 app.METHOD() 或 router.METHOD() 函数装入的中间件函数中有效。
那么next参数是什么东西?我打印了一下next参数:
var myLogger = function (req, res, next) {
console.log('LOGGED'+next);
next();
};
app.use(myLogger);
此时我们执行程序,那么在控制台你会得到这么一段代码:
function next(err) {
var layerError = err === 'route'
? null
: err;
// remove added slash
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
// restore altered req.url
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
// signal to exit router
if (layerError === 'router') {
setImmediate(done, null)
return
}
// no more matching layers
if (idx >= stack.length) {
setImmediate(done, layerError);
return;
}
// get pathname of request
var path = getPathname(req);
if (path == null) {
return done(layerError);
}
// find next matching layer
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
if (!route) {
// process non-route handlers normally
continue;
}
if (layerError) {
// routes do not match with a pending error
match = false;
continue;
}
var method = req.method;
var has_method = route._handles_method(method);
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
appendMethods(options, route._options());
}
// don't even bother matching route
if (!has_method && method !== 'HEAD') {
match = false;
continue;
}
}
// no match
if (match !== true) {
return done(layerError);
}
// store route for dispatch on change
if (route) {
req.route = route;
}
// Capture one-time layer values
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// this should be done for the layer
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
if (route) {
return layer.handle_request(req, res, next);
}
trim_prefix(layer, layerError, layerPath, path);
});
}
分析源代码:next函数内部有个while循环,每次循环都会从stack中拿出一个layer,这个layer中包含了路由和中间件信息,然后就会用layer和请求的path就行匹配,如果匹配成功就会执行layer.handle_request,调用中间件函数。但如果匹配失败,就会循环下一个layer(即中间件)。