Express中间件
1. 概念
- 中间件是指流程的中间处理环节
- 服务器收到请求后,可先调用中间件进行预处理
- 中间件是一个函数,包含
req, res, next三个参数,next()参数把流转关系交给下一个中间件或路由
2. 中间件注意事项
- 在注册路由之前注册中间件(错误级别中间件除外)
- 中间件可连续调用多个
- 别忘记调用
next()函数 next()函数后别写代码- 多个中间件共享
req、res对象
2. 全局中间件
通过调用 app.use()定义的中间件成为全局中间件,客户端发起的任何请求,到达服务器之后,都会触发的中间件
app.js
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')
})3. 局部中间件
不使用 app.use() 定义,在路由中使用,叫做局部生效的中间件
router.js
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('/hello', mw2, mw1, (req, res) => res.send('hello page.'))
app.get('/about', [mw1, mw2], (req, res) => res.send('about 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')
})4. 中间件的分类
1. 应用级别的中间件
通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件,叫做应用级别的中间件
//应用级别中间件(全局中间件)
app.use((req, res, next)=>{
next()
})
//应用级别中间件(局部中间件)
const mw1 = (req, res, next) => {
console.log('调用了第一个局部生效的中间件')
next()
}
app.get('/',mw1,(req,res)=>{
res.send('get')
})2. 路由级别的中间件
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上
app.js
const app = express()
const router = express.Router()
router.use(function (req, res, next) {
console.log(1)
next()
})
app.use('/', router)3. 错误级别的中间件
错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)
app.get('/',(req,res)=>{
throw new Error('error') //常规路由抛出错误
res.send('get')
})
app.use((err,req,res,next){ //错误中间件响应错误信息
res.send('error'+err.message)
})注意:错误级别的中间件,必须注册在所有路由之后
4. Express内置的中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
1.express.static()
- 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
2.express.json()
- 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
- 即客户端发送请求时,请求头为
'application/json'
app.use(express.json())
app.post('/user', (req, res) => {
//在服务器可以使用req.body属性来接收客户端发来的请求体数据
//默认情况下,不配置解析表单数据的中间件,则req.body默认等于undefined
res.send(req.body)
})
3.express.urlencoded()
- 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
- 即客户端发送请求时,请求头为
application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }))
app.post('/book', (req, res) => {
//在服务器可以使用req.body属性来接收客户端发来的请求体数据和url-excoded格式的数据
//默认情况下,不配置解析表单数据的中间件,则req.body默认等于undefined
res.send(req.body)
})
5. 第三方的中间件
非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。
例如:在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:
- 运行
npm install body-parser安装中间件 - 使用
require导入中间件 - 调用
app.use()注册并使用中间件
const parser=require('body-parser');
app.use(parser.urlencoded({extended:false}))
app.post('/user',(req,res)=>{
res.send(req.body)
})注意:Express 内置的 express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的

5. 自定义中间件
1. 需求描述与实现
步骤自己手动模拟一个类似于 express.urlencoded 这样的中间件,来解析 POST 提交到服务器的表单数据。
实现步骤:
- 定义中间件
- 监听 req 的 data 事件
- 监听 req 的 end 事件
- 使用 querystring 模块解析请求体数据
- 将解析出来的数据对象挂载为 req.body
- 将自定义中间件封装为模块
2. 定义中间件
使用 app.use() 来定义全局生效的中间件,代码如下:
app.use(funtion(req,res,next)=>{ ...中间件业务逻辑 })
3. 监听 req 的 data 事件
在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,
//定义标量,用来储存客户端发送过来的请求体数据
let str=''
//监听req对象的data事件(客户端发送过来的请求体数据)
req.on('data',(chunk)=>{ //chunk为客户端发送过来的数据
//拼接请求体数据,隐式转换为字符串
str+=chunk
})4. 监听 req 的 end 事件
当请求体数据接收完毕之后,会自动触发 req 的 end 事件。
因此,我们可以在 req 的 end 事件中,拿到并处理完整的请求体数据。示例代码如下:
//监听req对象的end事件(请求体发送完毕后自动触发)
req.on('end',()=>{
//打印完整请求体数据
console.log(str)
})5. 使用 querystring 模块解析请求体数据
Node.js 内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式。示例代码如下:
//导入处理querystring的node.js内置模块
const qs=require('querystring');
//调用qs.parse()方法,把查询字符串解析成对象格式
const body=qs.parse(str)6. 将解析出来的数据对象挂载为 req.body
上游的中间件和下游的中间件及路由之间,共享同一份 req 和 res。因此,我们可以将解析出来的数据,挂载为 req 的自定义属性,命名为 req.body,供下游使用。示例代码如下
req.on('end',()=>{
//调用qs.parse()方法,把查询字符串解析成对象格式
const body=qs.parse(str)
//将解析出来的数据,挂载为 req 的自定义属性
req.body=body
//执行业务逻辑
next()
})完整代码
const express=require('express')
const app=express()
const qs=require('querystring');
app.use((req,res,next)=>{
let str=''
req.on('data',(chunk)=>{
str+=chunk
})
req.on('end',()=>{
const body=qs.parse(str)
req.body=body
next()
})
})
app.post('/user',(req,res)=>{
res.send(req.body)
})
app.listen(80,()=>{
console.log('ok');
})
7. 将自定义中间件封装为模块
为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,示例代码如下:
custom-body-parser.js
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 //向外导出使用
const bodyParser=require('./12.custom-body-parser'); //导入
app.use(bodyParser) //注册
app.post('/user',(req,res)=>{ //响应
res.send(req.body)
})