1.中间件
中间件(Middleware ),特指业务流程的中间处理环节。
当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express 的中间件,本质上就是一个 function 处理函数,Express 中间件的格式如下:
1 2 3
| app.get('/', function(req, res, next){ next(); });
|
注意:中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res。
next()
函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
2.全局生效的中间件
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。
通过调用 app.use() 方法,即可定义一个全局生效的中间件
1 2 3 4 5 6 7 8 9
| const mw = function (req, res, next) { console.log('这是最简单的中间件函数') next() }
app.use(mw)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const express = require('express'); const app = express();
app.use((req, res, next) => { console.log('这是最简单的中间件函数'); next(); });
app.get('/', (req, res) => { console.log('调用了 / 这个路由'); res.send('Home page.'); }); app.get('/user', (req, res) => { console.log('调用了 /user 这个路由'); res.send('User page.'); });
app.listen(80, () => { console.log('http://127.0.0.1'); });
|
3.中间件的作用
多个中间件之间,共享同一份 req 和 res。基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const express = require('express'); const app = express();
app.use((req, res, next) => { const time = new Date(Date.now()); req.startTime = time; next(); })
app.get('/', (req, res) => { res.send('Home page.' + req.startTime); }) app.get('/user', (req, res) => { res.send('User page.' + req.startTime); })
app.listen(80, () => { console.log('http://127.0.0.1'); })
|
4.定义多个全局中间件
可以使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const express = require('express'); const app = express();
app.use((req, res, next) => { console.log('调用了第1个全局中间件'); next(); })
app.use((req, res, next) => { console.log('调用了第2个全局中间件'); next(); })
app.get('/user', (req, res) => { res.send('User page.'); })
app.listen(80, () => { console.log('http://127.0.0.1'); })
|
5.局部生效的中间件
不使用 app.use() 定义的中间件,叫做局部生效的中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const express = require('express')
const app = express()
const mw1 = (req, res, next) => { console.log('调用了局部生效的中间件') next() }
app.get('/', mw1, (req, res) => { res.send('Home page.') }) app.get('/user', (req, res) => { res.send('User page.') })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|
6.定义多个局部中间件
可以在路由中,通过如下两种等价的方式,使用多个局部中间件:
1 2
| app.get('/', [mw1, mw2], (req, res) => { res.send('Home page.') }); app.get('/', mw1, mw2, (req, res) => { res.send('Home page.') });
|
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
| const express = require('express')
const app = express()
const mw1 = (req, res, next) => { console.log('调用了第一个局部生效的中间件') next() }
const mw2 = (req, res, next) => { console.log('调用了第二个局部生效的中间件') next() }
app.get('/', [mw1, mw2], (req, res) => { res.send('Home page.') }) app.get('/user', (req, res) => { res.send('User page.') })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|
7.中间件的5个使用注意事项
一定要在路由之前注册中间件
客户端发送过来的请求,可以连续调用多个中间件进行处理
执行完中间件的业务代码之后,不要忘记调用 next() 函数
为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象
8.中间件的分类
应用级别的中间件
路由级别的中间件
错误级别的中间件
Express 内置的中间件
第三方的中间件
9.应用级别的中间件
通过 app.use()
或 app.get()
或 app.post()
,绑定到 app 实例上的中间件,叫做应用级别的中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| app.use((req, res, next) => { next(); });
const mw1 = (req, res, next) => { console.log('调用了第一个局部生效的中间件') next() }
const mw2 = (req, res, next) => { console.log('调用了第二个局部生效的中间件') next() }
app.get('/', [mw1, mw2], (req, res) => { res.send('Home page.') })
|
10.路由级别的中间件
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。
它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const express = require('express'); const app = express(); const router = express.Router();
router.use((req, res, next) => { console.log('time = ' + Date.now()); next(); })
router.get('/', (req, res) => { res.send('Home page'); })
app.use('/', router);
app.listen(80, () => { console.log('server is running at localhost'); })
|
11.错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。
注意:其他类型的中间件是注册在所有路由之前,但是错误级别的中间件,必须注册在所有路由之后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const express = require('express')
const app = express()
app.get('/', (req, res) => { throw new Error('服务器内部发生了错误!') res.send('Home page.') })
app.use((err, req, res, next) => { console.log('发生了错误!' + err.message) res.send('Error:' + err.message) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|
12.Express内置的中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
- express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(所有版本都可用)
- express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
- express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
1 2 3 4
| app.use(express.json())
app.use(express.urlencoded({ extended: false }))
|
(1) 表单中的 JSON 格式的数据: post请求的body体里的数据
(2) 表单中的 url-encoded 格式的数据: post请求的body体里的数据
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
| const express = require('express')
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => { console.log(req.body) res.send('ok') })
app.post('/book', (req, res) => { console.log(req.body) res.send('ok') })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|
13.第三方的中间件
由第三方开发出来的中间件,叫做第三方中间件。在项目中,可以按需下载并配置第三方中间件,从而提高项目的开发效率。
例如:在 express@4.16.0
之前的版本中,经常使用 body-parser
这个第三方中间件,来解析请求体数据。使用步骤如下:
- 运行 npm install body-parser 安装中间件
- 使用 require 导入中间件
- 调用 app.use() 注册并使用中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const express = require('express')
const app = express()
const parser = require('body-parser')
app.use(parser.urlencoded({ extended: false }))
app.post('/user', (req, res) => { console.log(req.body) res.send('ok') })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|
注意:Express 内置的 express.urlencoded
中间件,就是基于 body-parser
这个第三方中间件进一步封装出来的。
14.自定义中间件
自己手动模拟一个类似于 express.urlencoded
这样的中间件,来解析 POST 提交到服务器的表单数据。
实现步骤:
- 定义中间件
- 监听 req 的 data 事件
- 监听 req 的 end 事件
- 使用 querystring 模块解析请求体数据
- 将解析出来的数据对象挂载为 req.body
- 将自定义中间件封装为模块
(1) 监听 req 的 data 事件
在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。
(2) 监听 req 的 end 事件
当请求体数据接收完毕之后,会自动触发 req 的 end 事件。
可以在 req 的 end 事件中,拿到并处理完整的请求体数据。
(3) 使用 querystring 模块解析请求体数据
Node.js 内置了一个 querystring
模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式。
(4) 将解析出来的数据对象挂载为 req.body
上游的中间件和下游的中间件及路由之间,共享同一份 req 和 res。因此可以将解析出来的数据,挂载为 req 的自定义属性,命名为 req.body,供下游使用。
(1) custom_body_parser_module.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const qs = require('querystring')
const bodyParser = (req, res, next) => { let str = '' req.on('data', (chunk) => { str += chunk }) req.on('end', () => { const body = qs.parse(str) req.body = body next() }) }
module.exports = bodyParser
|
(2) test.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const express = require('express')
const app = express()
const customBodyParser = require('./custom_body_parser_module')
app.use(customBodyParser)
app.post('/user', (req, res) => { res.send(req.body) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1') })
|