NodeJS Web 框架 Express 之中间件

  • 参考
  • 描述
  • 中间件
      • next()
      • 一个简单的中间件函数
  • 使用
      • 全局中间件
      • 局部中间件
      • 共享
      • 注意事项
          • 位置
          • next()
  • 分类
      • 错误级中间件
      • 内置中间件
          • express.urlencoded()
          • express.json()
      • 第三方中间件

参考

项目描述
哔哩哔哩黑马程序员
搜索引擎Bing

描述

项目描述
Edge109.0.1518.61 (正式版本) (64 位)
NodeJSv18.13.0
nodemon2.0.20
Express4.18.2

中间件

中间件可以理解为实现某项功能的函数或是模块。在 Express 中,你可以使用一个或多个中间件来处理客户端的请求,并在最后对客户端的请求进行响应。

next()

在 Express 中,一个中间件若完成了它所需要完成的任务,则需要在该中间件内部调用函数 next() 以结束当前中间件的运行并告知 NodeJS 运行下一个中间件或路由。

一个简单的中间件函数

function mw(req, res, next){    console.log('【中间件函数】')    next()}

你可以为中间件函数提供三个参数以接收 Express 提供的三个实参,这三个实参分别为:

  1. 代表客户端的请求对象。
  2. 代表服务器端的响应对象。
  3. 接收 next() 函数,用于结束当前中间件的运行并告知 NodeJS 运行下一个中间件或路由。

使用

全局中间件

全局中间件将对所有的客户端请求进行处理,通过使用 use() 方法可以将函数注册为全局中间件。

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 创建中间件函数function mw(req, res, next){    console.log('【接收到客户端的请求】');    next()}// 注册中间件函数 mw 为全局中间件app.use(mw);// 设置路由app.get('/', (req, res) => {    res.send('Hello World');})app.get('/redheart', (req, res) => {    res.send('Hello World');})

在运行上述代码后,无论你访问链接 127.0.0.1:9090 还是 127.0.0.1:9090/redheart ,你都将终端上的控制台看到输出内容 【接收到客户端的请求】 并在响应页面中看到如下内容:

局部中间件

局部中间件不会处理所有的客户端请求,它们只会处理与相关路由(与该中间件绑定的路由)成功匹配的客户端请求。
在使用 app.get()app.post() 等函数时,你可以通过向这类函数提交多个中间件函数以将中间件注册为局部中间件。

app.get() 函数为例,如果你需要将中间件 mw1mw2 注册为该路由的局部中间件,可以参考如下用例:

app.get('/', mw1, mw2, (req, res) => {});// 或app.get('/', [mw1, mw2], (req, res) => {});

共享

在一个客户端请求中,reqres 是相同的,你可以通过为这两个对象定义属性以在不同的函数之间传递数据。

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 创建中间件函数function mw(req, res, next){    console.log('【接收到客户端的请求】');    next()}// 创建中间件哈数function mw1(req, res, next){    req.a = 10;    res.a = 36;    next();}function mw2(req, res, next){    req.b = req.a + res.a;    next();}// 创建路由app.get('/', mw1, mw2, (req, res) => {    res.send(req.a + ' + ' + res.a + ' = ' + req.b);});

执行结果:

注意事项

位置

在使用 Express 进行 Web 开发时,如果你需要使用到全局中间件,请将中间件放在路由之前,否则该中间件 可能 无法发挥功能。
路由与请求成功匹配便不会执行之后的函数,即使该路由对客户端的请求没有做出响应。

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 创建中间件函数function mw(req, res, next){    console.log('【接收到客户端的请求】');    next()}// 创建中间件哈数function mw1(req, res, next){    req.a = 10;    res.a = 36;    next();}function mw2(req, res, next){    req.b = req.a + res.a;    next();}// 创建路由app.get('/', mw1, mw2, (req, res) => {});// 在路由之后创建全局中间件app.use(() => {    console.log('【全局中间件】');})

执行结果:

在执行上述代码后,若你对 127.0.0.1:9090 进行访问,你将得到一个时刻旋转的加载动画:

分析:

浏览器正在等待路由的响应,但路由没有对此进行响应,也没有交出执行权。所以,对该次请求的处理到这里就已经结束了,这也导致了后面的全局中间件没有成功执行。
其实,你可以再提供一个参数给路由的回调函数以获取可以使用的 next() 函数。通过使用 next() 函数你可以在合适的时候将执行权交给后续函数或路由。

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 创建中间件函数function mw(req, res, next){    console.log('【接收到客户端的请求】');    next()}// 创建中间件哈数function mw1(req, res, next){    req.a = 10;    res.a = 36;    next();}function mw2(req, res, next){    req.b = req.a + res.a;    next();}// 创建路由app.get('/', mw1, mw2, (req, res, next) => {    next()});// 在路由之后创建全局中间件app.use(() => {    console.log('【全局中间件】');})

在执行上述代码后,若你对 127.0.0.1:9090 进行访问,你的终端将输出 【全局中间件】 ,这表明全局中间件已经成功执行。

虽然这样操作可以使得全局中间件在路由之后也可以正常执行,但这并不是全局中间件出现的初衷(对所有的客户端请求进行预处理),并且这样编写代码也不利于阅读。所以,在开发中请尽量将全局中间件放置在路由之前。

next()

在路由或中间件中调用 next() 函数后,仍可在路由或中间件中执行后续代码。

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 创建中间件函数function mw(req, res, next){    console.log('【接收到客户端的请求】');    next()}// 创建路由app.get('/', (req, res, next) => {    res.send('Hello World');    next();    console.log('Finish');})

在执行上述代码后,若你对 127.0.0.1:9090 进行访问,服务器将正常响应客户端的请求(发送 Hello World)。在执行 next() 后,终端将输出 Finish

虽然使用 next() 函数后,还可以执行当前中间件或路由的后续代码,但请不要这样做,这样会降低代码的可读性。

分类

Express 官方将中间件进行了分类,具体如下:

项目描述
应用级中间件app.get()app.post()app.all() 等绑定在 app 上的函数都可以认为是应用级别的中间件。
路由级中间件用于构建服务器的路由系统。
错误级中间件用于捕获在处理客户端请求过程中所产生的错误。
内置中间件由 Express 官方提供的内置中间件。
第三方中间件由第三方提供的中间件。

由于应用级中间件与路由级中间件已经在本人的本篇博客及其他博客中已讲述,因此本文后续将重点关注后三类中间件。

错误级中间件

你需要向错误级中间件的回调函数提供四个参数 err, req, res, next ,其中,err 用于接收处理客户端请求的过程中引发的错误所对应的错误对象。

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 创建错误级中间件函数function mw(err, req, res, next){    console.log('【发现错误】');}// 将错误级中间件注册为全局中间件app.use(mw);// 创建路由app.get('/', (req, res, next) => {// 人为产生错误    throw new Error('Error');    res.send('Hello World');})

执行结果:

运行上述代码后,你如果访问 127.0.0.1:9090 将得到如下内容:

浏览器中显示了错误信息。显然,错误级中间件并没有成功捕获到错误。

分析:

在上述示例中,我们在路由之后对错误级中间件进行了全局注册。但正确的使用方式应该是将错误级中间件放置在所有的中间件及路由之后。

执行过程:

Express 在执行过程中如果发现了错误,会在此行(引发错误的代码行)向下寻找错误级中间件,如果没有找到错误级中间件,NodeJS 将抛出错误。
所以请尽量在所有中间件及路由之后注册错误级中间件,这样能够提高服务器的稳定性,避免宕机。

正确示范:

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 创建错误级中间件函数function mw(err, req, res, next){    console.log('【发现错误】');}// 创建路由app.get('/', (req, res, next) => {// 人为产生错误    throw new Error('Error');    res.send('Hello World');})// 将错误级中间件注册为全局中间件app.use(mw);

运行上述代码后,你将在终端中得到如下内容:

【发现错误】

内置中间件

Express 提供了如下内置中间件:

项目描述
express.json()该函数用于处理客户端以 POST 方式提交的 application/json 格式的数据。
express.urlencoded()该函数用于处理客户端以 POST 方式提交的 application/x-www-form-urlencoded 格式的数据。
express.static()该函数用于托管服务器中的静态资源。

注:

  1. express.json()express.urlencoded() 仅在 Express 4.16.0 以上的版本可用,在进行后续的示例前请查看你的版本。如果你不清楚你的 Express 的版本,你可用打开项目中的 package.json 文件(该文件是由 npm 生成的项目信息文件)进行查找。

我能够在 package.json 文件中查找到如下信息,这说明我的 Express 的版本号为 4.18.2

“express”: “^4.18.2”

  1. 如果你使用的 Express 的版本比 4.16.0 更低,你可用使用第三方中间件 body-parser 来对客户端通过 POST 方式提交的多种格式的数据进行处理。你可以通过如下命令使用 npm 来安装该第三方中间件:
npm install body-parser
  1. express.json()express.urlencoded() 是 Express 基于第三方模块 body-parser 进行封装的。
express.urlencoded()

在此我将使用 Python 来通过 POST 方式向服务器端发送数据。当然你也可以使用 PostmanBurpSuiteHackbar v2 等工具向服务器端发送 POST 数据。

服务器端代码

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 注册 express.urlencoded() 中间件以处理客户端// 以 POST 方式发送的 // application/x-www-form-urlencoded 格式的数据// extended: false 使该中间件不要解析被嵌套的数据app.use(express.urlencoded({ extended: false} ));// 创建路由app.post('/', (req, res, next) => {    // 你可以通过 req.body 获取客户端发送的    // application/x-www-form-urlencoded 格式的数据    // 如果你没有使用相关的中间件处理 POST 请求    // 数据,req.body 将为 undefined。    res.send(req.body);})

客户端代码

# 导入 Python 第三方模块 requests 以发送请求import requests# 定义需要传递的数据data = {'name': 'RedHeart', 'age': 18, 'gender': 'male'}# 发送 POST 请求response = requests.request('post', 'http://127.0.0.1:9090', data=data)# 对服务器端的响应信息进行打印print(response.text)

注:

  1. 提交给 requests.request() 函数的 URL 需要表明使用的协议(使用 http://127.0.0.1:9090 而不是 127.0.0.1:9090),否则 Python 将抛出如下错误:

InvalidSchema: No connection adapters were found for ‘127.0.0.1:9090’

  1. 虽然提交给服务器端的数据的类型为一个字典,但 requests 会将其转换为 x-www-form-urlencoded 格式,requests 默认以这种方式发送 POST 请求。

  2. x-www-form-urlencoded 的格式如下(以客户端提交的数据进行演示):

name=RedHeart&age=18&gender=male

执行效果:

在执行上述客户端代码后,Python 将在终端中打印如下内容:

{“name”:“RedHeart”,“age”:“18”,“gender”:“male”}

express.json()

服务器端代码

const express = require('express');const app = express();// 监听本机的 9090 端口app.listen(9090);// 注册 express.urlencoded() 中间件以处理客户端// 以 POST 方式发送的 // application/x-www-form-urlencoded 格式的数据// extended: false 使该中间件不要解析被嵌套的数据app.use(express.urlencoded({ extended: false} ));// 创建路由app.post('/', (req, res, next) => {    // 你可以通过 req.body 获取客户端发送的    // application/x-www-form-urlencoded 格式的数据    // 如果你没有使用相关的中间件处理 POST 请求    // 数据,req.body 将为 undefined。    res.send(req.body);})

客户端代码:

import requests# 导入 Python 第三方模块 json 以将字典类型的数据转换为 json 格式的数据import jsondata = json.dumps({'name': 'RedHeart', 'age': 18, 'gender': 'male'})# 设置请求头以告知浏览器我们传递的是 application/json 的数据headers = {'content-type': 'application/json'}response = requests.request('post', 'http://127.0.0.1:9090', data=data, headers=headers)print(response.text)

执行效果:

在执行上述客户端代码后,Python 将在终端中打印如下内容:

{“name”:“RedHeart”,“age”:“18”,“gender”:“male”}

第三方中间件

你可以通过 npm(NodeJS Package Manger) 包管理器下载并安装第三方中间件并通过 require() 函数将其导入。第三方中间件的使用方式与内置中间件的使用方式类似。