Null's Blog

express中的路由

好久之前就用过express,其实也没那么久吧,转前端的时候用nodejs,就顺便写过几个helloworld了。
还买了一本书,Node.js 实战,还好,这本书虽然内容跟XX天精通XX差不多,但至少它不标榜“XX天”,“精通”这些一看就让人受不了的标题。入门看这些还是挺好的,前端体系更新过快,快速学习,快速适应就是王道。
不记得买了多久了,应该是一出就买了,听说这本书出的时候express即将更新到4.0+版本了,也就是说,按照处女座的观点,该书一出就过时了。不知道现在是否出了新版。
书中前面一大段来至nswbmw的N-blognswbmw的N-blog即是引导我入门express的最好事例,没有之一。希望稿费能分nswbmw一部分吧。
废话不多说,言归正传。
本文基于express4+版本进行记录

路由

路由是指如何定义应用的端点(URIs)以及如何响应客户端的请求。
路由是由一个 URI、HTTP 请求(GET、POST等)和若干个句柄组成,它的结构如下: app.METHOD(path, [callback…], callback), app 是 express 对象的一个实例, METHOD 是一个 HTTP 请求方法, path 是服务器上的路径, callback 是当路由匹配时要执行的函数。
我们知道HTTP请求常用的只有两种,get和post,而路由作为响应请求的方法,也有对应的类型,当然,这里也仅讨论响应get和post请求的路由。
路由(功能)可以挂载到app实例上,路由(router)亦是一个完整的中间件和独立的系统,可以理解为小型的app实例。个人感觉express4允许这样做是一个多余的设计,express力求简单高效,没必要让同样的功能允许使用两种不同的方式去使用。
本博客目前全部将路由功能挂在至app实例上:

1
2
3
4
5
//app.js
var list_routes = require('./routes/list'); //列表
var article_routes = require('./routes/article'); //查看文章页
var login_routes = require('./routes/login'); //登录页
var post_routes = require('./routes/post'); //发表页

最后使用:

1
2
3
4
5
//app.js
list_routes(app);
article_routes(app);
login_routes(app);
post_routes(app);

将实例传递至各自的路由模块中。
这里主要以列表模块进行说明
根据设计思路,列表页最终抽象出三大块路由组,分别对应博客列表的三块功能,普通列表,tag列表和archive列表。每个列表又包含分页,所以仅目前列表模块就挂载了13个响应方法,或者说句柄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
app.get('/',function(req,res,next){
//默认第1页
});
app.get('/page',function(req,res,next){
res.redirect(301,'/page/1/'); //当用户查看了http://www.nullcn.com/page 跳转到第1页
});
app.get('/page/:page',function(req,res,next){
res.redirect(301,'/page/'+req.params.page+'/'); //http://www.nullcn.com/page/2 重定向至http://www.nullcn.com/page/2/
});
app.get('/page/:page/',function(req,res,next){
//真正处理分页的功能代码
});
/*后面部分跟普通分页思路类似 就不再做解释说明了*/
app.get('/tag/:tag',function(req,res,next){
res.redirect(301,'/tag/'+req.params.tag+'/');
});
app.get('/tag/:tag/',function(req,res,next){
});
app.get('/tag/:tag/page/:page',function(req,res,next){
res.redirect(301,'/tag/'+req.params.tag+'/page/'+req.params.page+'/');
});
app.get('/tag/:tag/page/:page/',function(req,res,next){
});
app.get('/:year/:month',function(req,res,next){
res.redirect(301,'/'+req.params.year+'/'+req.params.month+'/');
});
app.get('/:year/:month/',function(req,res,next){
});
app.get('/:year/:month/page',function(req,res,next){
res.redirect(301,'/'+req.params.year+'/'+req.params.month+'/page/1/');
});
app.get('/:year/:month/page/:page',function(req,res,next){
res.redirect(301,'/'+req.params.year+'/'+req.params.month+'/page/'+req.params.page+'/');
});
app.get('/:year/:month/page/:page/',function(req,res,next){
});

注意:默认情况下,http://www.nullcn.com/page/2与http://www.nullcn.com/page/2/ express4会认为是同一个路由,但是,熟悉mvc url与uri概念的人应该知道,这两个地址所表达的东西是不同的,所以要通过如下代码,让express区分这两者,并特别注意,需要在app实例挂载某些中间件之前执行,否则无效(坑了我两小时,我会说?):

1
2
3
4
5
//app.js
// 区分 /page/2 与 /page/2/ 的区别 并且该行不能在任何中间件之后 否则无效
app.enable('strict routing');
//app.set('strict routing',true); //同上
//console.log(app.enabled('strict routing'));

之后,即可以通过:

1
2
3
app.get('/page',function(req,res,next){
res.redirect(301,'/page/1/'); //当用户查看了http://www.nullcn.com/page 跳转到第1页
});

防止用户手贱删个斜杠,导致无法正常处理的问题。

代码中的req与res这里就不在详细说明,只再说下next的功能。
next用于将请求传递至下一个处理句柄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.param('id', function (req, res, next, id) {
console.log('CALLED ONLY ONCE');
next();
})
app.get('/user/:id', function (req, res, next) {
console.log('although this matches');
next();
});
app.get('/user/:id', function (req, res) {
console.log('and this matches too');
res.end();
});
//最终输入如下:
//CALLED ONLY ONCE
//although this matches
//and this matches too

由此我个人判断,可以应用至404统一跳转,权限统一验证等等地方。