Skip to content

Express中间件

1. 概念

  • 中间件是指流程的中间处理环节
  • 服务器收到请求后,可先调用中间件进行预处理
  • 中间件是一个函数,包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由

2. 中间件注意事项

  • 在注册路由之前注册中间件(错误级别中间件除外)
  • 中间件可连续调用多个
  • 别忘记调用 next() 函数
  • next() 函数后别写代码
  • 多个中间件共享 reqres对象

2. 全局中间件

通过调用 app.use()定义的中间件成为全局中间件,客户端发起的任何请求,到达服务器之后,都会触发的中间件

app.js

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

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 实例上的中间件,叫做应用级别的中间件

js
//应用级别中间件(全局中间件)
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

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)

js
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'
js
app.use(express.json())

app.post('/user', (req, res) => {
    //在服务器可以使用req.body属性来接收客户端发来的请求体数据
    //默认情况下,不配置解析表单数据的中间件,则req.body默认等于undefined
    res.send(req.body)
})

image-20240521164902311

3.express.urlencoded()

  • 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
  • 即客户端发送请求时,请求头为application/x-www-form-urlencoded
js
app.use(express.urlencoded({ extended: false }))

app.post('/book', (req, res) => {
    //在服务器可以使用req.body属性来接收客户端发来的请求体数据和url-excoded格式的数据
    //默认情况下,不配置解析表单数据的中间件,则req.body默认等于undefined
    res.send(req.body)
})

image-20240521164912671

5. 第三方的中间件

非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载配置第三方中间件,从而提高项目的开发效率。

例如:在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:

  1. 运行 npm install body-parser 安装中间件
  2. 使用 require 导入中间件
  3. 调用 app.use() 注册并使用中间件
js
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 这个第三方中间件进一步封装出来的

image-20240521164919811

5. 自定义中间件

1. 需求描述与实现

步骤自己手动模拟一个类似于 express.urlencoded 这样的中间件,来解析 POST 提交到服务器的表单数据。

实现步骤:

  1. 定义中间件
  2. 监听 req 的 data 事件
  3. 监听 req 的 end 事件
  4. 使用 querystring 模块解析请求体数据
  5. 将解析出来的数据对象挂载为 req.body
  6. 将自定义中间件封装为模块

2. 定义中间件

使用 app.use() 来定义全局生效的中间件,代码如下:

app.use(funtion(req,res,next)=>{ ...中间件业务逻辑 })

3. 监听 req 的 data 事件

在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。

如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,

js
//定义标量,用来储存客户端发送过来的请求体数据
let str=''
//监听req对象的data事件(客户端发送过来的请求体数据)
req.on('data',(chunk)=>{    //chunk为客户端发送过来的数据
    //拼接请求体数据,隐式转换为字符串
    str+=chunk
})

4. 监听 req 的 end 事件

当请求体数据接收完毕之后,会自动触发 req 的 end 事件。

因此,我们可以在 req 的 end 事件中,拿到并处理完整的请求体数据。示例代码如下:

js
//监听req对象的end事件(请求体发送完毕后自动触发)
req.on('end',()=>{
    //打印完整请求体数据
      console.log(str)
    })

5. 使用 querystring 模块解析请求体数据

Node.js 内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式。示例代码如下:

js
//导入处理querystring的node.js内置模块
const qs=require('querystring');

//调用qs.parse()方法,把查询字符串解析成对象格式
const body=qs.parse(str)

6. 将解析出来的数据对象挂载为 req.body

上游的中间件和下游的中间件及路由之间,共享同一份 req 和 res。因此,我们可以将解析出来的数据,挂载为 req 的自定义属性,命名为 req.body,供下游使用。示例代码如下

js
req.on('end',()=>{
        //调用qs.parse()方法,把查询字符串解析成对象格式
        const body=qs.parse(str)
        //将解析出来的数据,挂载为 req 的自定义属性
        req.body=body
        //执行业务逻辑
        next()
    })

完整代码

js
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');
})

image-20240521164929362

7. 将自定义中间件封装为模块

为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,示例代码如下:

custom-body-parser.js

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    //向外导出

使用

js
const bodyParser=require('./12.custom-body-parser');    //导入

app.use(bodyParser)    //注册

app.post('/user',(req,res)=>{    //响应
    res.send(req.body)
})

MIT Licensed