文章目录
- 中间件
- 定义一个最简单的中间件
- 自定义中间件
- 中间件的五个使用注意事项
Express 基于 Connect 构建而成,因此,它也保持了重用中间件来完成基础任务的想法。这就意味着,通过 Express 的 API 方便地构建 Web 应用地同时,又不失构建于 HTTP 模块之上高可用中间件地生态系统。
中间件
由于 Express 是构建于 Connect 之上地,所以,当创建 Express 服务器时可以使用 Connect 兼容地中间件。比如,要托管 image/ 目录下的图片,就可以像这样使用 static 中间件
app.use(express.static(__dirname + '/images'))
注意了,在引入了 Express 之后就可以直接使用 Connect 的中间件了。不需要 require(‘connect’)或者把 connect 作为项目依赖添加到 package.json 文件中。
定义一个最简单的中间件
Express 中间件本质上就是一个 function 处理函数,其具体结构如下:
这个 next() 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或者路由。
// 定义一个最简单的中间件
const mw = (req,res,next)=>{console.log('这是最简单的一个中间件')// 把流转关系,转交给下一个中间件或者是路由next()
}
自定义中间件
- 定义中间件
- 监听 req 的 data 事件
- 监听 req 的 end 事件
- 使用 querystring 模块解析请求体数据
- 讲解洗出来的数据对象挂载为 req.body
- 将自定义中间件封装为模块
下面我们手动模拟一个类似于 express.urlencoded
这样的中间件,来解析 POST 提交到服务器的表单数据。
首先我们定义一个全局中间件
const express = require('express')
const app = express()
// 这是解析表单数据的中间件
app.use((req, res, next) => {//定义中间件具体的业务逻辑
})
app.listen(80,()=>{console.log('Express server running at http://127.0.0.1')
})
第二步我们来编写监听 req 的 data 事件的代码
在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,每一次触发
data 事件时,获取到数据只是完整数据的一部分,需要手动对接受到的数据进行拼接。
代码如下:
// 1、定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2、监听 req 的 data 事件
req.on('data', (chunk) => {str += chunk
})
第三步我们来编写监听 req 的 end 事件的代码
当请求体数据接收完毕之后,会自动触发 req 的 end 事件。
因此,我们可以在 req 的 end 事件中,拿到并处理完整的请求体数据。
代码如下:
// 3、监听 req 的 end 事件
req.on('end', () => {//在 str 中存储的就是完整的请求体数据console.log(str)// TODO: 把字符串格式的请求体数据,解析成对象格式
})
使用 querystring 模块解析请求体数据
Node.js 中内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式。
//导入 querystring 模块
const qs = require('querystring')req.on('end', () => {const body = qs.parse(str)console.log(body)
})
第四步将解析出来的数据对象挂载为 req.body
上游的中间件和下游的中间件以及路由之间,共享用一份 req 和 res。因此,我们可以将解析出来的数据挂在为 req 的自定义属性,命名为 req.body ,供下游使用。
在 end 中写进代码:
req.on('end', () => {const body = qs.parse(str)console.log(body)req.body = bodynext()
})
并且将 res.send('ok')
更改成 res.send(req.body)
最后一步将自定义中间件封装为模块
为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块。
先定义一个常量将 res.use() 内的内容封装起来,然后再向外导入
代码如下:
const bodyParser = ((req, res, next) => {//...
})
module.exports = bodyParser
完整代码
// 导入 querystring 模块
const qs = require('querystring')
// 这是解析表单数据的中间件
const bodyParser = ((req,res,next)=>{// 定义中间件具体的业务逻辑// 1、定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据let str = ''// 2、监听 req 的 data 事件req.on('data',(chunk)=>{str += chunk})// 3、监听 req 的 end 事件req.on('end',()=>{// 在 str 中存储的就是完整的请求体数据// TODO: 把字符串格式的请求体数据,解析成对象格式const body = qs.parse(str)req.body = bodynext()})
})
module.exports = bodyParser
至此,自定义一个中间件已经全部完成,现在只需要在 test.js 中进行导入就可使用。
测试代码如下:
const express = require('express')
const app = express()
//导入自己封装好的中间件模块
const customBody = require('./类urlencoded的中间件')
//将自定义的2中间件函数,注册成为全局可用的中间件
app.use(customBody)
app.post('/book',(req,res)=>{res.send(req.body)
})
app.listen(80,()=>{console.log('Express server running at http://127.0.0.1')
})
中间件的五个使用注意事项
- 一定要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用 next() 函数
- 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
- 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象