4.3 路由
路由是指应用程序如何根据指定的路由路径和指定的HTTP请求方法(GET和POST等)来处理请求。
在Express应用中,每个路由可以有一个或多个处理函数,这些函数会在路由匹配时执行。
路由采用以下结构定义:
app.METHOD(PATH, HANDLER)
· app:应用实例。
· METHOD:小写get和post等的HTTP请求方法。
· PATH:路由路径。
· HANDLER:路由匹配时执行的函数。
4.3.1 路由方法
路由方法是从HTTP方法派生的,以下是GET和POST方法定义路由的示例:
app.get('/', (req, resp) => { resp.send('GET请求'); }); app.post('/', (req, resp) => { resp.send('POST请求'); });
Express支持所有的HTTP请求方法:
· get
· post
· head
· options
· delete
· put
· patch
如果需要使用一个路由来处理所有的请求方法,则可以调用app.all():
app.all('/', (req, resp) => { resp.send('请求首页'); });
4.3.2 路由路径
路由路径结合请求方法,定义了可以发出请求的地址。路由路径可以是字符串、字符串模式或正则表达式。
查询字符串(一般称为GET参数)不是路由路径的一部分。
(1)以下是一些基于字符串的路由路径的示例。
以下路由路径会将请求匹配到根路由/:
app.get('/', (req, resp) => { resp.send('主页'); });
以下路由路径会将请求匹配到/about:
app.get('/about', (req, resp) => { resp.send('关于页面'); });
以下路由路径可以将请求匹配到/random.txt文件:
app.get('/random.txt', (req, resp) => { resp.send('random.txt'); });
字符串模式可以“认为”是正则表达式的子集,支持部分正则表达式语法。
(2)以下是一些基于字符串模式的路由路径示例。
以下路由路径将与/acd和/abcd相匹配。“?”号在正则表达式中代表“至多一个”,示例代码中就是匹配“至多一个b”:
app.get('/ab?cd', (req, resp) => { resp.send('ab?cd'); });
以下路由路径将与/abcd、/abbcd、/abbbcd等等相匹配。+在正则表达式中代表“至少一个”,示例代码中就是匹配“至少一个b”:
app.get('/ab+cd', (req, resp) => { resp.send('ab+cd'); });
以下路由路径将与/abcd和/ad相匹配。()在正则表达式中代表分组,示例代码中就是“要么有一个bc,要么没有bc”:
app.get('/a(bc)?d', (req, resp) => { resp.send('a(bc)?d'); });
(3)以下是一些基于正则表达式的路由路径示例。
以下路由路径将与任何带有user的请求链接相匹配:
app.get(/user/, (req, resp) => { resp.send('/user/'); });
以下路由路径将严格与/admin匹配:
app.get(/^\/admin$/, (req, resp) => { resp.send('/^\/admin$/'); });
4.3.3 路由参数
路由参数用于捕获URL中各位置的值。捕获的值将填充到req.params对象中,并将路由路径中指定的route参数名称作为req.params对象的键。
app.get('/users/:userId/timelines/:timelineId', (req, resp) => { resp.json(req.params); });
访问:http://localhost:8080/users/1/timelines/1,将得到以下响应:
{ "userId": "1", "timelineId": "1" }
路由参数的名称必须由[A-Za-z0-9_](也就是大小写字母、数字、下画线)组成。
字符串和字符串模式路由路径中的中划线“-”和点“.”无特殊意义,Express按照字面意思处理这两个字符。因为在正则表达式中这两个字符有特殊意义,特此说明以防止混淆。
app.get('/users/:firstName.:lastName', (req, resp) => { resp.json(req.params); });
访问http://localhost:8080/users/lei.xia,将得到以下响应:
{ "firstName": "lei", "lastName": "xia" }
在/users/:userId/timelines/:timelineId例子中,预期匹配的是数字ID类型的参数,但是由于没有类型限制,字符串形式的参数也会匹配到。如果不限制参数类型,容易引发类型问题。
在路由参数后使用正则表达式可以限制参数类型,不满足类型的参数无法匹配该路由。
比如上述例子中,我们限制userId和timelineId为数字类型,可以使用以下代码:
app.get('/users/:userId(\\d+)/timelines/:timelineId(\\d+)', (req, resp) => { resp.json(req.params); });
访问http://localhost:8080/users/1/timelines/1,将得到以下响应:
{ "userId": "1", "timelineId": "1" }
访问http://localhost:8080/users/1a/timelines/1,将得到以下响应:
Cannot GET /users/1a/timelines/1
因为1a与(\d+)不匹配。
4.3.4 路由函数
路由方法和路由路径匹配之后就会执行对应的路由函数,路由函数的签名如下:
function(request, response, next)
· request Express:请求对象。
· response Express:响应对象。
· next:匹配的下一个路由函数(可选参数)。
1.单个路由函数
最简单的路由函数,对请求处理后发出响应,结束本次请求处理。
app.get('/', (req, resp) => { resp.send('/'); });
2.多个路由函数
对于同一个路由,可以定义多个路由函数来处理,每个路由函数做一项工作。
不要忘记next()方法的调用,即使定义多个路由函数,只要第一个函数未调用next(),后续的路由函数都不会执行。
app.get('/', (req, resp, next) => { console.log(`${req.method} ${req.path}`); next(); }, (req, resp) => { resp.send('首页'); });
如下示例定义了两个路由函数,第一个函数打印了当前请求方法和请求路径,然后调用next()执行下一个路由函数以响应此次请求。在上面的示例中,访问首页时终端会输出“GET /”,之后会向客户端输出“首页”字样。Express中还可以使用函数数组来定义多个路由函数,上述例子的另外一种写法如下,两种写法效果是一样的。
function logger(req, resp, next) { console.log(`${req.method} ${req.path}`); next(); } function home(req, resp) { resp.send('首页'); } // 设置路由 app.get('/', [logger, home]);
3.公共路由路径
如果多个路由有同样的路由路径,只是请求方法不同,若分别为每种方法定义一次路由,则不利于模块化。
app.get('/user/login', (req, resp) => { resp.send('登录页面'); }); app.post('/user/login', (req, resp) => { resp.send('登录处理'); });
针对这种典型场景,Express提供了app.router()来处理:
4.模块化的路由
使用Express提供的Router对象可以创建模块化的对象,实现路由和入口JS的解耦。使用模块化的路由有以下优点:
· 便于维护。
· 统一的路由前缀。
· 模块化。
user.js用户相关路由:
const express = require('express'); const router = express.Router(); router.get('/login', (req, resp) => { resp.send('登录'); }); router.get('/register', (req, resp) => { resp.send('注册'); }); // 导出路由对象 module.exports = router;
timeline.js动态相关路由:
const express = require('express'); const router = express.Router(); router.get('/list', (req, resp) => { resp.send('动态列表'); }); // 导出路由对象 module.exports = router;
index.js入口文件:
上述例子最终会生成以下路由:
· GET /user/login。
· GET /user/register。
· GET /timeline/list。