Express 路由
Express路由是指定如何响应客户端请求的方式。你可以使用Express应用程序对象的方法来定义路由,这些方法对应于HTTP方法,比如使用app.get()
处理GET请求,app.post()
处理POST请求等等。你也可以使用app.all()
来处理所有HTTP方法,以及使用app.use()
来指定中间件作为回调函数。
这些路由方法指定了一个回调函数,当应用程序收到指定路由和HTTP方法的请求时就会调用它。换句话说,应用程序会监听与指定路由和方法匹配的请求,一旦匹配到,就会调用指定的回调函数。
实际上,路由方法可以有多个回调函数作为参数。在有多个回调函数的情况下,你需要向回调函数提供next
作为参数,然后在函数体内部调用next()
将控制权交给下一个回调函数。
下面的代码是一个非常基础的路由示例:
// 引入 Express 模块
const express = require('express');
// 创建 Express 应用程序实例
const app = express();
// 当向主页发出 GET 请求时,以 "hello world "作为响应
app.get('/', (req, res) => {
res.send('hello world');
});
路由方法
路由方法是从HTTP方法中派生出来的,并附加到 Express 类的一个实例上。
以下代码是针对应用程序根目录定义的GET和POST方法的示例:
// GET 方法路由
app.get('/', (req, res) => {
res.send('GET 请求到达首页')
})
// POST 方法路由
app.post('/', (req, res) => {
res.send('POST 请求到达首页')
})
Express支持与所有HTTP请求方法对应的方法:get、post等等。还有一个特殊的路由方法 app.all()
,用于在路径上为所有 HTTP 请求方法加载中间件函数。例如,以下处理程序用于处理对路径”/secret”的所有请求,无论使用GET、POST、PUT、DELETE还是 http
模块中支持的其他任何HTTP请求方法:
app.all('/secret', (req, res, next) => {
console.log('Accessing the secret section ...')
next() // 将控制权传递给下一个处理程序
})
路由路径
路由路径是与请求方法结合在一起的,它定义了可以接收请求的端点。路由路径可以是字符串、字符串模式或正则表达式。
在路由路径中,字符 ?, +, *, 和 () 是正则表达式的一部分。连字符 (-) 和点号 (.) 在基于字符串的路径中直接解释。
如果要在路径字符串中使用美元符号 ($),需要将其转义并用([ 和 ])包围起来。例如,对于路径 “/data/$book” 的请求,路径字符串将是 “/data/([$])book”。
查询字符串不是路由路径的一部分。
以下是一些基于字符串的路由路径示例:
// 这个路由路径匹配根路由,即 /
app.get('/', (req, res) => {
res.send('root');
});
// 这个路由路径匹配 /about
app.get('/about', (req, res) => {
res.send('about');
});
// 这个路由路径匹配 /random.text
app.get('/random.text', (req, res) => {
res.send('random.text');
});
以下是一些基于字符串模式的路由路径示例:
// 这个路由路径匹配 acd 和 abcd
app.get('/ab?cd', (req, res) => {
res.send('ab?cd');
});
// 这个路由路径匹配 abcd, abbcd, abbbcd, 等等
app.get('/ab+cd', (req, res) => {
res.send('ab+cd');
});
// 这个路由路径匹配 abcd, abxcd, abRANDOMcd, ab123cd, 等等
app.get('/ab*cd', (req, res) => {
res.send('ab*cd');
});
// 这个路由路径匹配 /abe 和 /abcde
app.get('/ab(cd)?e', (req, res) => {
res.send('ab(cd)?e');
});
以下是一些基于正则表达式的路由路径示例:
// 这个路由路径匹配带有“a”字符的任何内容
app.get(/a/, (req, res) => {
res.send('/a/');
});
// 这个路由路径匹配 butterfly 和 dragonfly,但不匹配 butterflyman, dragonflyman,等等
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/');
});
通过理解这些概念,开发人员可以有效地在 Express 应用程序中定义路由路径,处理各种 HTTP 请求。
路由参数
路由参数是 URL 中的命名部分,用于捕获在 URL 中指定位置的值。捕获的值会填充到 req.params
对象中,其中路由参数在路径中指定的名称作为它们各自的键。
比如说,有这样一个路由路径:/users/:userId/books/:bookId
。当访问 http://localhost:3000/users/34/books/8989
这样的URL时,Express会自动将 userId
设置为 34
,bookId
设置为 8989
,然后存储在 req.params
中,以便在处理请求时使用。
要定义带有路由参数的路由,只需在路径中指定参数的名称即可。例如:
app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params);
});
路由参数的名称必须由字母、数字或下划线组成,且不能以数字开头。除了字母、数字和下划线外,还可以在参数名称中使用破折号(-)和点号(.)。
比如,如果有这样的路由路径:/flights/:from-:to
,对应的请求URL是 http://localhost:3000/flights/LAX-SFO
,那么 req.params
将会是 { "from": "LAX", "to": "SFO" }
。
如果想要更精确地控制参数的匹配规则,可以在参数名后面加上一个正则表达式。例如,/user/:userId(\d+)
可以匹配数字形式的用户ID,如请求URL http://localhost:3000/user/42
,req.params
将会是 { "userId": "42" }
。
需要注意的是,因为正则表达式通常是字面字符串的一部分,所以在使用时需要注意转义。如果想匹配一个或多个数字,正则表达式是 \\d+
。
在 Express 4.x 中,正则表达式中的 *
字符并不按照通常的方式解释。作为替代方案,可以使用 {0,}
来匹配任意数量的任意字符,这个问题在 Express 5 中可能会得到修复。
路由处理程序
路由处理程序就像是一系列函数,用于处理不同的请求。你可以给一个路由指定一个函数,也可以给它指定多个函数。这些函数会按顺序执行,但如果其中一个函数决定不再继续处理这个路由,它可以调用 next('route')
来跳过后面的函数。
首先,来看一个简单的例子。我们有一个路由 /example/a
,且给它指定了一个函数,当有请求访问这个路由时,这个函数就会被调用,然后返回一条消息。
app.get('/example/a', (req, res) => {
res.send('Hello from A!')
})
接下来,看一个稍微复杂一点的例子。在这个例子中,有一个路由 /example/b
,并给它指定了两个函数。第一个函数会在请求到来时打印一条消息,然后调用 next()
,将控制权传递给下一个函数。第二个函数接收到控制权后,会返回另一条消息。
app.get('/example/b', (req, res, next) => {
console.log('the response will be sent by the next function ...')
next()
}, (req, res) => {
res.send('Hello from B!')
})
还有一种情况是,给一个路由指定一个函数数组。这个数组中的函数会按顺序执行,每个函数都可以决定是否继续处理这个路由。
const cb0 = function (req, res, next) {
console.log('CB0')
next()
}
const cb1 = function (req, res, next) {
console.log('CB1')
next()
}
const cb2 = function (req, res) {
res.send('Hello from C!')
}
app.get('/example/c', [cb0, cb1, cb2])
最后,我们还可以将独立的函数和函数数组组合在一起来处理一个路由。这种方式可以更灵活地组织代码,以便处理不同的情况。
const cb0 = function (req, res, next) {
console.log('CB0')
next()
}
const cb1 = function (req, res, next) {
console.log('CB1')
next()
}
app.get('/example/d', [cb0, cb1], (req, res, next) => {
console.log('the response will be sent by the next function ...')
next()
}, (req, res) => {
res.send('Hello from D!')
})
响应方法
响应对象(res)上的方法可以向客户端发送响应,并终止请求-响应循环。如果路由处理程序没有调用这些方法,客户端请求将被挂起。下面是一些常用的响应方法及其描述:
方法 | 描述 |
---|---|
res.download() | 提示用户下载文件。 |
res.end() | 结束响应过程,不再发送任何数据。 |
res.json() | 发送 JSON 格式的响应数据。 |
res.jsonp() | 发送支持 JSONP 的 JSON 格式的响应数据。 |
res.redirect() | 重定向到另一个 URL。 |
res.render() | 渲染视图模板并发送给客户端。 |
res.send() | 发送各种类型的响应数据,可以是字符串、对象、数组等。 |
res.sendFile() | 将文件以二进制流形式发送给客户端,通常用于发送文件。 |
res.sendStatus() | 设置响应的状态码,并将对应的状态信息发送给客户端。 |
app.route()
app.route()
方法可以让你为同一个路由路径创建多个处理函数,这些处理函数可以按顺序执行。这种方法有助于减少重复的代码,并且让路由路径的管理更加清晰。
下面是一个例子:
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')
})
首先,创建了一个 /book
的路由路径,然后分别指定了处理 GET 请求、POST 请求和 PUT 请求的函数。这样,对于同一个路由路径,可以在一个地方集中管理不同请求的处理方式。
express.Router 类
使用 express.Router
类可以创建模块化的、可挂载的路由处理程序。一个 Router 实例是一个完整的中间件和路由系统;因此,它经常被称为“mini-app”。
下面是一个示例,创建了一个作为模块的路由,并在其中加载了一个中间件函数,定义了一些路由,并将路由模块挂载到主应用程序中的路径上。
首先,在 app 目录中创建一个名为 birds.js
的路由文件,代码如下:
const express = require('express')
const router = express.Router()
// 此路由的中间件
const timeLog = (req, res, next) => {
console.log('Time: ', Date.now())
next()
}
router.use(timeLog)
// 定义主页路由
router.get('/', (req, res) => {
res.send('Birds home page')
})
// 定义about页面路由
router.get('/about', (req, res) => {
res.send('About birds')
})
// 导出路由模块,以便在应用中加载使用
module.exports = router
然后,在应用中加载路由模块:
// 导入路由模块
const birds = require('./birds')
// ...
// 使用路由模块,并指定挂载路径
app.use('/birds', birds)
这样,当用户访问 /birds
和 /birds/about
时,路由模块中定义的路由将会被触发,并且 timeLog
中间件函数也会被调用并记录请求时间。