当前位置: 首页 > news >正文

Express学习笔记(二)——Express中间件

目录

1. 中间件的概念

1.1 什么是中间件

1.2 现实生活中的例子

1.3 Express 中间件的调用流程

1.4 Express 中间件的格式

1.5 next 函数的作用

2. Express 中间件的初体验

2.1 定义中间件函数

2.2 全局生效的中间件

2.3 定义全局中间件的简化形式

2.4 中间件的作用

2.5 定义多个全局中间件

2.6 局部生效的中间件

2.7 定义多个局部中间件

2.8 了解中间件的5个使用注意事项

3. 中间件的分类

3.1 应用级别的中间件

3.2 路由级别的中间件

3.3 错误级别的中间件

3.4 Express内置的中间件

3.5 第三方的中间件

4. JSON与URL-encoded请求体格式详解(补充)

4.1 JSON 格式(application/json)

4.2 URL-encoded 格式(application/x-www-form-urlencoded)

5. 自定义中间件

5.1 需求描述与实现步骤

5.2 定义中间件

5.3 监听 req 的 data 事件

5.4 监听 req 的 end 事件

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

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

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


1. 中间件的概念

1.1 什么是中间件

中间件(Middleware ),特指 业务流程 中间处理环节

1.2 现实生活中的例子

在处理污水的时候,一般都要经过 三个处理环节 ,从而保证处理过后的废水,达到排放标准。
处理污水的这三个中间处理环节,就可以叫做中间件。

1.3 Express 中间件的调用流程

当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行 预处理。

1.4 Express 中间件的格式

Express 的中间件, 本质 上就是一个 function 处理函数 ,Express 中间件的格式如下:
注意:
  • 中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res。

1.5 next 函数的作用

next 函数 是实现 多个中间件连续调用 的关键,它表示把流转关系 转交 给下一个 中间件 路由

2. Express 中间件的初体验

2.1 定义中间件函数

可以通过如下的方式,定义一个最简单的中间件函数:

2.2 全局生效的中间件

客户端发起的 任何请求 ,到达服务器之后, 都会触发的中间件 ,叫做全局生效的中间件。
通过调用 app.use( 中间件函数 ) ,即可定义一个 全局生效 的中间件,示例代码如下:

2.3 定义全局中间件简化形式

2.4 中间件的作用

多个中间件之间, 共享同一份 req res 。基于这样的特性,我们可以在 上游 的中间件中, 统一 为 req 或 res 对象添加 自定义 属性 方法 ,供 下游 的中间件或路由进行使用。
const express = require('express')

const app = express()

// 定义一个全局的中间件
app.use((req, res, next) => {
  // 获取到请求到达服务器的时间
  const time = Date.now()
  // 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由
  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('express server running at http://127.0.0.1')
})

2.5 定义多个全局中间件

可以使用 app.use() 连续定义多个 全局中间件。客户端请求到达服务器之后,会按照中间件 定义的先后顺序 依次进行调用,示例代码如下:

2.6 局部生效的中间件

不使用 app.use() 定义的中间件,叫做 局部生效的中间件 ,示例代码如下:

2.7 定义多个局部中间件

可以在路由中,通过如下两种 等价 的方式, 使用多个局部中间件

2.8 了解中间件的5个使用注意事项

  • ① 一定要在路由之前注册中间件
  • ② 客户端发送过来的请求,可以连续调用多个中间件进行处理
  • ③ 执行完中间件的业务代码之后,不要忘记调用 next() 函数
  • ④ 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
  • ⑤ 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象

3. 中间件的分类

为了方便大家 理解 记忆 中间件的使用,Express 官方把 常见的中间件用法 ,分成了 5 大类 ,分别是:
  • 应用级别的中间件
  • 路由级别的中间件
  • 错误级别的中间件
  • Express 内置的中间件
  • 第三方的中间件

3.1 应用级别的中间件

通过 app.use() app.get() app.post() 绑定到 app 实例上的中间件 ,叫做应用级别的中间件,代码示例如下:

3.2 路由级别的中间件

绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过, 应用级别中间件是绑定到 app 实例上 路由级别中间件绑定到 router 实例上 ,代码示例如下:

3.3 错误级别的中间件

错误级别中间件的 作用
  • 专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式
  • 错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。

注意:
  • 错误级别的中间件,必须注册在所有路由之后

演示错误级别的中间件:

const express = require('express')

const app = express()

app.get('/', (req, res) => {
  // 1.1 人为的制造一个错误
  throw new Error('服务器内部发生了错误!')
  res.send('home page')
})

// 2. 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
  console.log('发生了错误!' + err.message)
  res.send('Error:' + err.message)
})

app.listen(80, () => {
  console.log('express server running at http://127.0.0.1')
})

3.4 Express内置的中间件

自 Express 4.16.0 版本开始,Express 内置了 3 个 常用的中间件,极大的提高了 Express 项目的开发效率和体验:
  • express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
// 在这里调用 express.static() 方法,快速的对外提供静态资源
app.use('/abc', express.static('./files'))

app.use(express.static('./clock'))
  • express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

  • express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

代码演示:

const express = require('express')

const app = express()

// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())

// 通过 express.urlencoded() 这个中间件,来解析表单中的 url-encoded 格式的数据
app.use(express.urlencoded({extended: false}))

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

app.post('/book', (req, res) => {
  // 在服务器端,可以通过 req.body 来获取 JSON 格式的表单数据 和 url-encoded 格式的数据
  console.log(req.body)
  res.send('ok')
})

app.listen(80, () => {
  console.log('express server running at http://127.0.0.1')
})

3.5 第三方的中间件

非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以 按需下载 配置 第三方中间件,从而提高项目的开发效率。
例如:在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件,来解析请求体数据。使用步骤如下:
  • ① 运行 npm install body-parser 安装中间件
  • ② 使用 require 导入中间件
  • ③ 调用 app.use() 注册并使用中间件
const express = require('express')
const app = express()

// 1. 导入解析表单数据的中间件 body-parser
const parser = require('body-parser')

// 2. 使用 app.use() 注册中间件
app.use(parser.urlencoded({ extended: false}))

app.post('/user', (req, res) => {
  // 如果没有配置任何解析表单数据的中间件,则 req.body 默认等于 undefined
  console.log(req.body)
  res.send('ok')
})

app.listen(80, () => {
  console.log('express server running at http://127.0.0.1')
})
注意:
  • Express 内置的 express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的。

4. JSON与URL-encoded请求体格式详解(补充)

4.1 JSON 格式(application/json

  • 定义与特点

    • 数据格式:基于键值对的嵌套结构,支持对象、数组、字符串、数字、布尔值等数据类型。
    • 编码方式:直接使用 Unicode 字符(如 UTF-8),无需对特殊字符(如 @空格)进行转义。
    • 可读性:结构化清晰,适合人类阅读和调试。
  • 语法示例

{
  "name": "Alice",
  "age": 28,
  "isStudent": false,
  "hobbies": ["coding", "music"],
  "address": {
    "city": "Shanghai",
    "postcode": "200000"
  }
}
  • 适用场景

    • 复杂数据:需要传递嵌套对象或数组(如配置信息、用户资料)。
    • 前后端分离:现代 Web 应用和移动端 API 交互。
    • 非表单数据:如日志上报、批量操作、业务逻辑数据。
  • HTTP 请求示例

POST /api/users HTTP/1.1
Content-Type: application/json

{
  "username": "alice",
  "password": "p@ssw0rd"
}
  • 前后端处理

    • 前端发送

      • // 使用 Fetch API
        fetch('/api/users', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ username: 'alice', password: 'p@ssw0rd' }),
        });
        
        // 注意:必须手动调用 JSON.stringify() 序列化数据
    • 后端解析(以 Node.js/Express 为例):
      • const express = require('express');
        const app = express();
        
        // 需启用 JSON 解析中间件
        app.use(express.json());
        
        app.post('/api/users', (req, res) => {
          console.log(req.body.username); // 直接获取 JSON 数据
        });

  • 注意事项

    • Content-Type 必须正确:若未设置 application/json,后端可能无法解析。

    • 不支持文件上传:JSON 无法直接传输二进制文件(需用 Base64 编码或改用 multipart/form-data)。

    • 数据冗余:字段名重复时会占用额外空间(如数组中的多个对象)。

4.2 URL-encoded 格式(application/x-www-form-urlencoded

  • 定义与特点

    • 数据格式:扁平化的键值对,用 & 分隔多个参数,格式为 key1=value1&key2=value2

    • 编码方式:特殊字符(如空格、@:)需转义为 %XX 形式(如空格 → %20@ → %40)。

    • 可读性:编码后的字符串难以直接阅读。

  • 语法示例

name=Alice&age=28&isStudent=false&hobbies=coding&hobbies=music&address.city=Shanghai&address.postcode=200000
  • 注意
    • URL-encoded 本身不支持嵌套对象或数组,但可通过约定实现(如 address.city 表示嵌套字段)。
  • 适用场景

    • HTML 表单提交:浏览器默认的表单编码方式。

    • 简单键值对:无嵌套结构的参数(如登录、搜索)。

    • 兼容旧系统:传统 API 或需要 URL 参数复用的场景(如 GET 请求的 Query String)。

  • HTTP 请求示例

POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=alice&password=p%40ssw0rd

前后端处理

  • 前端发送

    • // 使用 URLSearchParams 自动编码
      const params = new URLSearchParams();
      params.append('username', 'alice');
      params.append('password', 'p@ssw0rd');
      
      fetch('/login', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: params,
      });
      
      // 或手动拼接字符串(需注意转义)
      body: 'username=alice&password=p%40ssw0rd'
  • 后端解析(以 Node.js/Express 为例):
    • const express = require('express');
      const app = express();
      
      // 需启用 URL-encoded 解析中间件
      app.use(express.urlencoded({ extended: true })); // extended: true 允许解析嵌套对象
      
      app.post('/login', (req, res) => {
        console.log(req.body.username); // 直接获取参数
      });
  • 注意事项
    • 编码必须正确:前端需处理特殊字符转义(如 @ → %40),否则后端可能解析错误。
    • 不支持复杂结构:嵌套对象或数组需通过约定(如 hobbies[]=coding&hobbies[]=music)或后端框架扩展(如 express.urlencoded({ extended: true }))。
    • 数据体积较大:重复的键名和转义字符会增加传输量。

5. 自定义中间件

5.1 需求描述实现步骤

自己 手动模拟 一个类似于 express.urlencoded 这样的中间件,来 解析 POST 提交到服务器的表单数据
实现步骤:
  • ① 定义中间件
  • ② 监听 req 的 data 事件
  • ③ 监听 req 的 end 事件
  • ④ 使用 querystring 模块解析请求体数据
  • ⑤ 将解析出来的数据对象挂载为 req.body
  • ⑥ 将自定义中间件封装为模块

5.2 定义中间件

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

5.3 监听 req 的 data 事件

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

如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后分批发送到服务器。所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。

5.4 监听 req 的 end 事件

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

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

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

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

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

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

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

const app = express()

// 这是解析表单数据的中间件
app.use((req, res, next) => {
  // 定义中间件具体的业务逻辑
  // 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
  let str = ''
  // 2. 监听 req 的 data 事件
  req.on('data', (chunk) => {
    str += chunk
  })

  // 3.监听 req 的 end 事件
  req.on('end', () => {
    // 在 str 中存放的是完整的请求体数据
    // console.log(str)  //bookname=%E8%A5%BF%E6%B8%B8%E8%AE%B0&author=%E5%90%B4%E6%89%BF%E6%81%A9&gender=%E7%94%B7
    // todo: 把字符串格式的请求体数据,解析为对象格式
    const body = qs.parse(str)
    console.log(body)
    req.body = body
    next()
  })
})

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

app.listen(80, () => {
  console.log('express server is running at http://127.0.0.1')
})

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

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

相关文章:

  • Mlivus Cloud SDK v2技术革新与最佳实践指南
  • 无人机等非合作目标公开数据集2025.4.3
  • Why are we forced to download Apps?
  • Nextjs15 实战 - React Notes之SidebarNoteList优化和Suspense的使用
  • Qwen-7B-Chat 本地化部署使用
  • 【计算机网络】Linux配置SNAT策略
  • 【PowerQuery专栏】List.Combine 进行数据合并操作
  • 【Linux笔记】进程管理章节笔记
  • 20250405在荣品的PRO-RK3566开发板使用Rockchip原厂的buildroot系统来适配gmac1
  • Android学习总结之算法篇四(排序)
  • 第十三章:持久化存储_《凤凰架构:构建可靠的大型分布式系统》
  • gltf unity-Unity中Gltf模型的使用与优化技巧
  • Envoy 源码解析(三):Envoy 发送数据到服务端、Envoy 收到服务端响应
  • 回归预测 | Matlab实现NRBO-Transformer-LSTM多输入单输出回归预测
  • 水文传输规约 SL651的相关经验
  • Java的Selenium的特殊元素操作与定位之iframe切换
  • Spring Boot开发三板斧:高效构建企业级应用的核心技法
  • 【项目管理-高项】学习方法 整体概览
  • 优化 Web 性能:处理非合成动画(Non-Composited Animations)
  • Java的Selenium基本元素定位(findElement方法)