Express Routing(路由系统)
路由在 Express 基础中有讲到过,是指应用程序如何根据客户端对特定的端点的请求做出响应,这个端点由一个URI(统一资源标识符,即路径)和一种特定的 HTTP 请求方法(GET、POST 等)共同组成。
定义路由
Express 使用的 app 对象的方法定义路由,方法名称对应 HTTP methods。例如用 app.get() 处理 GET 请求,用 app.post() 处理 POST 请求。
路由定义的方式如下:
app.METHOD(path, callback [, callback ...])
我们可以看到,Express 是通过调用 express 实例上挂载的方法,传递路由路径(端点)以及回调函数(处理函数)来定义路由的,Express 应用接受到请求时,会遍历匹配所有的路由规则,一旦方法和路径都能匹配上某一个路由,那么就会执行它的回调函数。
路由方法
Express 的路由方法源自于 HTTP methods,它是被挂载在 express 实例上的,下面的代码定义了一个 GET 方法的路由。
javascript
app.get('/', (req, res) => {
res.send('Hello World!')
})
// 请求服务器根路径会得一个 'Hello World!' 的响应
Express 支持所有的 HTTP methods,大家可以在官网的 API 参考页面查看所有的 app.METHOD 方法。
app.all()
app.all() 是一个特殊的路由方法,它可以用来处理所有的 HTTP methods (不区分 GET、POST、PUT、DELETE 等),只要请求路径匹配,就会执行对应的处理函数。
我们可以指定一个中间件来作为他的回调函数来做全局操作或进行路径前置处理,不用重复写代码。
中间件是一个签名为 (req, res, next) => {} 的函数,函数内部里必须写 next(),请求才会继续往下匹配,其实上面示例中的 (req, res) => { res.send('Hello World!') }也是一个中间件,只不过针对某一个请求响应完毕就不要继续再做其他操作了,就没有调用 next(),后续会写一篇详细介绍中间件的文章。
下面是一些 app.all() 常见的一些应用场景:
javascript
// 所有 /api/* 路径的请求,统一处理跨域
app.all('/api/*', (req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type");
// 继续执行后面的路由
next();
});
// 所有 /admin/* 必须先登录
app.all('/admin/*', (req, res, next) => {
if (!req.session.user) {
return res.send('请先登录');
}
next(); // 校验通过,继续执行
});
// 放在所有路由最后,匹配所有未处理的请求
app.all('*', (req, res) => {
res.status(404).send('页面不存在');
});
路由路径
路由路径跟请求方法相结合,定义了可接受请求的端点。
路由路径可以是字符串、字符串模式(带匹配符的字符串)或正则表达式。
javascript
// 字符串
app.get('/', (req, res) => {
res.send('root')
})
// 字符串模式
// 匹配 abcd, abxcd, abRANDOMcd, ab123cd 等这种路径
app.get('/ab*cd', (req, res) => {
res.send('ab*cd')
})
// 正则表达式
app.get(/a/, (req, res) => {
res.send('/a/')
})
路由参数
路由参数是用于捕获 URL 中对应位置指定值的命名 URL 片段。捕获到的值会填充到 req.params 对象中,路径中指定的路由参数名作为其各自的键。
Route path: /users/:userId/books/:bookId
Request URL: http://localhost:3000/users/34/books/8989
req.params: { "userId": "34", "bookId": "8989" }
要使用路由参数定义路由,只需如下所示,在路由路径中指定路由参数即可。
javascript
app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params)
})
注意:路由参数命名只能是数字、字母或者下划线
老版本的 Express 中,如果你想要给路由参数加一些限制可以在参数后面加上 (正则表达式),但是现在 最新的 Express 中 path-to-regexp 已经 不支持 这种写法了,我们可以在回调函数中进行参数格式的校验。
Route path: /user/:userId(\d+)
Request URL: http://localhost:3000/user/42
req.params: {"userId": "42"}
PS:官网指南里面介绍了这个方法,还好我试了一下,发现不能用了。
路由处理器
事实上我们除了传递单个函数作为回调函数之外,我们可以提供多个回调函数来处理请求,但是他们必须得想中间件一样调用 next(),另外如果某些条件下不应该继续执行当前的路由,就可以调用 next('route') 来跳过剩余的路由回调函数,将控制权交给 next 传递的后续的路由。
javascript
app.get('/user/:id', (req, res, next) => {
if (req.params.id === '0') {
return next('route')
}
res.send(`User ${req.params.id}`)
})
app.get('/user/:id', (req, res) => {
res.send('Special handler for user ID 0')
})
路由处理器可以是一个或多个函数,也可以是一个函数数组,也可以是函数和函数数组的组合。
响应方法
响应对象(req)上的方法可以发送一个响应信息给客户端,并终止请求 - 响应循环(不再执行后续任何的中间件 / 路由)。如果路由回调函数中没有调用响应方法,客户端请求得不到响应将一直被挂起。
| 方法 | 描述 |
|---|---|
| res.download() | 提示下载文件 |
| res.end() | 结束响应进程 |
| res.json() | 发送一个 JSON 响应 |
| res.jsonp() | 发送一个支持 JSONP 的 JSON 响应 |
| res.redirect() | 重定向一个请求 |
| res.render() | 加载一个显示模板 |
| res.send() | 发送各种类型的响应 |
| res.sendFile() | 发送文件流(八位字节流) |
| res.sendStatus() | 设置响应状态码,并将其状态码字符串作为响应体发送 |
app.route()
我们可以使用 app.route() 创建一个可链式调用的路由处理器,由于路径只在一处定义,创建模块化路由十分方便,同时还能减少冗余代码与拼写错误。
javascript
app.route('/book')
.get((req, res) => {
res.send('Get a random book')
})
.post((req, res) => {
res.send('Add a book')
})
.put((req, res) => {
res.send('Update the book')
})
express.Router
我们可以使用 express.Router 类创建一个模块化、可挂载的路由处理器,Router 实例是一个完整的中间件和路由系统;因此,它通常被称为一个 "mini-app"。
我们来看一下官网的示例:
typescript
const express = require('express')
const router = express.Router()
// middleware that is specific to this router
const timeLog = (req, res, next) => {
console.log('Time: ', Date.now())
next()
}
router.use(timeLog)
// define the home page route
router.get('/', (req, res) => {
res.send('Birds home page')
})
// define the about route
router.get('/about', (req, res) => {
res.send('About birds')
})
module.exports = router
示例创建了一个路由模块,在其中加载中间件函数、定义若干路由,并将该路由模块挂载到主应用的某个路径上。
我们可以在其他应用中加载这个路由
typescript
const birds = require('./birds')
// ...
app.use('/birds', birds)
这个应用现在可以处理 /birds 和 /birds/about 的请求,同时还可以调用该路由专用的 timeLog 中间件函数
但如果父路由 /birds 包含路径参数,默认情况下子路由是无法访问到这些参数的。要让子路由可以访问,你需要在创建 Router 时传入 mergeParams 选项。
javascript
const router = express.Router({ mergeParams: true })