一、中间件的概念
1. 什么是中间件
中间介, 特指 业务流程 的 中间处理环节。
2. 中间件生活中的例子
在处理污水的时候,一般都要经过 三个处理环节, 从而保证处理过后的废水,达到排放标准。
处理污水的这三个中间处理环节,就可以叫做中间介。
3. Express 中间件的 调用流程
当一个请求到达 express 的服务器之后 ,可以连续调用多个中间件,从而对这次请求进行 预处理。
4. Express 中间件的格式
Express 的中间件,本质 上就是一个 funciton 处理函数, Express 中间件的格式如下:
注意:中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res.
5. 中间中的 next 函数的作用
next 函数 是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
二、Express 中间件的体验
1.定义一个最简单的中间件函数
// 定义一个中间介函数, 常量mv 所指向的,就是一个中间介函数
const mw = function (req, res, next) {
//注意:在当前中间介的业务处理完毕后,必须调用 next()函数
// 表示把流转关系转交给下一个中间介或路由
next();
};
2. 全局生效的中间件
a. 全局生效的中间件
客户端发起的任何请求,到达服务器之后, 都会触发的中间件,叫做全局生效的中间件。
通过调用 app.use(中间件函数),即可定义一个 全局生效 的中间件。示例代码如下:
// 定义一个中间介函数, 常量mv 所指向的,就是一个中间介函数
const mw = function (req, res, next) {
//注意:在当前中间介的业务处理完毕后,必须调用 next()函数
// 表示把流转关系转交给下一个中间介或路由
console.log("经过了中间介函数");
next();
};
// 使用 app.use(mv) 将 mw 注册为全局中间介
app.use(mw);
完整代码:
b. 定义全局中间件的简化形式
// app.use() 内部直接传一个中间介函数
app.use((req, res, next)=>{
console.log('中间介')
next()
});
完整代码:
c. 定义多个全局中间件
可以使用 app.use() 连续定义多个全局中间件。 客户端请求到达服务器之后 ,会按照中间件定义的先后顺序依次进行调用,示例代码如下:
// 注册第 1 个全局中间介
app.use((req, res, next)=>{
console.log('注册第 1 个全局中间介')
next()
});
// 注册第 2 个全局中间介
app.use((req, res, next)=>{
console.log('注册第 2 个全局中间介')
next()
});
app.get("/list", (req, res) => {
res.send("list 列表" );
});
完整代码:
3. 中间件的作用
多个中间件之间,共享同一份 req 和 res, 基于这样的特点, 我们可以在上游的中间件中,统一为 req 或 res 对象添加 自定义的 属性或方法, 供下游的中间件或路由进行使用。
const express = require("express");
const app = express();
app.use((req, res, next)=>{
// 为 req 添加 time 属性,供下游的中间介或路由使用
req.time = Date.now();
next()
});
app.get("/list", (req, res) => {
res.send("list 列表" + req.time);
});
app.post("/add", (req, res) => {
res.send("add 增加"+ req.time);
});
app.listen(80, () => {
console.log(`runing in http://127.0.0.1`);
});
4. 局部生效的中间件
a. 定义局部生效的中间件
不使用 app.use() 定义的中间件,叫做 局部生效的中间件,示例代码如下:
// 定义一个中间件的函数
const mw = (req, res, next) => {
console.log('调用了中间件的函数')
next()
}
// mw 这个中间件只在'当前路由中生效',这种用法属于’局部生效的中间件‘
app.get('/', mw, (req, res)=> {
res.send('/ 路径')
})
// mw 这个中间件不会影响下面这个路由
app.get("/list", (req, res) => {
res.send("list 路径" );
});
b.定义多个局部中间件
可以在路由中,通过如下两种 等价 的方式,使用多个局部中间件:
# 以下两种写法 是完全等价的
app.get('/', mw1, mw2, (req, res)=> {
res.send('/ 路径')
})
app.get('/', [mw1, mw2], (req, res)=> {
res.send('/ 路径')
})
完整代码:
5. 中间件的 5 个使用注意事项
- 一定要在
路由之前注册中间件 - 客户端发送过来的请求,
可以连续调用多个中间件进行处理 - 执行完中间件的业务代码之后,不要忘记调**
用 next() 函数** - 为了**
防止代码逻辑混乱**,调用 next() 函数之后不要再写额外的代码 - 连续调用多个中间件时,多个中间件之间,
共享 req 和 res 对象
三、中间件的分类
1. 中间件的分类
Express 官方把常见的中间件用法,分成了 5 大类,分别是:
应用级别的中间件路由级别的中间件错误级别的中间件Express内置的中间件第三方的中间件
2. 应用级别的中间件
应用级别的中间件,通过 app.use() 或 app.get() 或 app.post(),绑定到 app 实例上的中间件,叫做 应用级别的中间件。示例代码如下:
// 应用级别的中间件(全局中间件)
app.use((req, res, next)=>{
next()
})
// 应用级别的中间件(局部中间件)
app.get('/', mw1, (req, res)=>{
res.send('Home Page')
})
3. 路由级别的中间件
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。
只不过,应用级别中间件是绑定到 app 实例上, 路由级别中间件是绑定到 router 实例上,代码示例如下:
var app = express()
var router = express.Router()
// 路由级别的中间件
router.use((req, res, next) {
next()
})
app.use('./', router)
4. 错误级别的中间件
错误级别中间件的 作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常奔溃的问题。
格式:错误级别中间件的 function 处理函数中, 必须有 4 个形参,形参顺序从前到后,分别是(err, req, res, next)。
const express = require("express");
const app = express();
app.get('/', (req, res)=> { // 路由
throw new Error('服务器内部发生错误') // 抛出了一个自定义错误
res.send('/ 路径')
})
// 定义一个错误级别的中间介,并且错误级别的中间介代码需放在路由代码的后面
app.use((err, req, res, next) => {
res.send(`Error:${err.message}`)
next()
})
app.listen(80, () => {
console.log(`runing in http://127.0.0.1`);
});
5. Express 内置的中间件
自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:
1. express.static 快速托管静态资源的内置中间件,例如:HTML 文件、图片、Css 样式等。(无兼容性)
2. express.join 解析 JSON 格式的请求体数据(有兼容性:仅在 4。26.0+ 版本中可用)
3. express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4。26.0+ 版本中可用)
// 配置解析 application/json 格式数据的内置中间件
app.use(express.join())
// 配置解析 application/x-www.form-urlencoded 格式数据的内置中间件
app.use(express.urlencoded({extended: false}))
a. 内置中间件的使用
const express = require("express");
const app = express();
// 调用 express.json() 来解析 JSON 格式的请求体数据
app.use(express.json())
// 调用 express.urlencoded 来解析 URL-encoded 格式的请求体数据
app.use(express.urlencoded({extends:false}))
app.post('/', (req, res) => {
// 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表彰数据的中间介,则 req.body 默认等于 undefined
console.log(req.body, 'body')
res.send(req.body)
})
app.post('/book', (req, res)=>{
console.log(req.body)
res.send(req.body)
})
app.listen(80, () => {
console.log(`runing in http://127.0.0.1`);
});
6. 第三方的中间件
非 Express 官方内置的,而是由第三方开发出来的中间介,叫做第三方中间介。在项目中, 可以 按需下载 并配置 第三方中间件。从而提高项目的开发效率。
例如:在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间,来解析请求体数据。使用步骤如下:
- 运行
npm i body-parser安装中间件 - 使用
require导入中间件 - 调用**
app.use()** 注册并使用中间件
注意: Express 内置的 express.urlencoded 中间件, 就是基于 body-parser 这个第三方中间件进一步封装出来的。
示例代码如下:
const express = require("express");
const app = express();
// 导入 第三方中间介 body-parser
const parser = require('body-parser')
// 调用 express.urlencoded 来解析 URL-encoded 格式的请求体数据
app.use(parser.urlencoded({extends:false}))
app.post('/book', (req, res)=>{
console.log(req.body)
res.send(req.body)
})
app.listen(80, () => {
console.log(`runing in http://127.0.0.1`);
});
7. 自定义中间件
a. 需求描述与实现步骤
自己 手动模拟 一个类似于 express.urlencoded 这样的中间件,来 解析 POST 提交到服务器的表单数据。实现步骤:
- 定义中间件
- 监听 req 的 data 事件
- 监听 req 的 end 事件
- 使用 querystring 模块解析请求体数据
b. 定义中间件
使用 app.use() 来定义全局生效的中间件,代码如下:
app.use((req, res, next) {
// 中间件的业务逻辑
})
c. 监听 req 的 data 事件
在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕,则 客户端会把数据切割后,分批发送给服务器。所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收的数据进行拼接。
// 定义变量,用来存储客户端发送过来的请求体数据
let str = ''
// 监听 req 对象的 data 事件(客户端发送过来的新的请求体数据)
req.on('data', (chunk)=>{
// 拼接请求体数据,隐式转换为字符串
str +=chunk
})
d. 监听 req 的 end 事件
当请求体数据 接收完毕之后 ,会自动触发 req 的 end 事件。
因此, 可在在 req 的 end 事件中,拿到并处理完整的请求体数据。示例代码如下:
// 监听 req 对象的 end 事件(请求体发送完毕后自动触发)
req.on('end', ()=>{
// 打印完整的请求体数据
console.log(str)
// 接下来,把字符串格式的请求体数据,解析成对象格式
})
e. 使用 querystring 模块解析请求体数据
Node.js 内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式。示例代码如下:
// 导入处理 querystring 的 Node.js 内置模块
const qs = require('querystring')
// 调用 qs.parse() 方法,把查询字符串解析成对象
const body = qs.parse(str)
f. 将解析出来的数据对象挂载为 req.body
上游 的 中间件 和 下游 的 中间件及路由 之间,共享同一份 req 和 res。因此,可以将解析出来的数据,挂载为 req 的自定义属性,命名为 req.body, 供下游使用。示例代码如下:
req.on('end', ()=>{
// 调用 qs.parse() 方法,把查询字符串解析成对象
const body = qs.parse(str)
// 将解析出来的请求体对象,挂载为 req.body 属性
req.body = body
// 最后,一定要调用 next() 函数,执行后续的业务逻辑
next()
})
g. 完整代码
自室义中间件:custom-body-parser.js
# custom-body-parser.js
// 导入处理 querystring 的 node.js 内置模块,可把客户端传过来的字符串参数解析成对象格式
const qs = require('querystring');
const customBodyParser = (req, res, next) => {
// 中间介的业务逻辑
// 定义 str 变量,用来存储客户端发送过来的数据
let str = '';
// 监听 req 对象的 data 事件
req.on('data', (chunk)=>{
// 拼接请求数据,并隐式转换成字符串
str +=chunk;
})
// 监听 req 对象的 end 事件
req.on('end', () => {
// 在 end 事件里可以拿到完整的请求数据,解析成对象格式
console.log(str) // age=18&username=xxl&gender=%E7%94%B7
let body = qs.parse(str)
console.log(body, '参数') // { age: '18', username: 'xxl', gender: '男' }
// 把解析出来的参数对象挂载到 req.body 属性上
req.body = body;
next()
})
}
module.exports = customBodyParser;
index.js
# index.js
const express = require("express");
const app = express();
// 导入自定义中间化,解析客户端传过来的参数
const qs = require('./custom-body-parser')
app.use(qs)
app.post('/list', (req, res) => {
res.send(req.body)
})
app.listen(80, () => {
console.log(`runing in http://127.0.0.1`);
});