NodeJS 中 Express 之中间件

news2025/1/12 12:07:54

NodeJS 中 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 json

data = 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() 函数将其导入。第三方中间件的使用方式与内置中间件的使用方式类似。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/179216.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【web前端】盒子模型

border 边框 content 内容 padding内边距 margin外边距 1.边框 border 边框粗细 用px作为单位 border-style : solid 实线的 dashed虚线的 dotted 点的 边框的符合写法: 那三个没有先后顺序 边框可以分开写 表格的细线边框 border-collapse …

【编程入门】开源记事本(微信小程序版)

背景 前面已输出多个系列: 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 本系列对比云笔记,将更为简化,去掉了网络调用&#xff0…

20230126英语学习

Your Dog’s Behavior Is a Product of Their Genes 狗狗做什么,基因来决定 这篇好难,字基本都认识,但它不认识我~ “Identification of the genes behind dog behavior has historically been challenging,” says first author Emily Dut…

【计算机网络(考研版)】第一站:计算机网络概述(一)

目录 一、计算机网络的概念 1.计算机网络的定义 2.计算机网络的组成 3.计算机网络的功能 4.计算机网络的分类 二、计算机网络的性能指标 1.速率 2.带宽 3.时延 4.时延带宽积 5.往返时间 6.利用率 三、计算机网络的体系结构 1.体系结构 2.协议 3.服务 4.接口&a…

活动星投票优秀支书网络评选微信的投票方式线上免费投票

“优秀支书”网络评选投票_多人投票流程顺序_视频投票图文投票_微信比赛投票小程序近些年来,第三方的微信投票制作平台如雨后春笋般络绎不绝。随着手机的互联网的发展及微信开放平台各项基于手机能力的开放,更多人选择微信投票小程序平台,因为…

最详细、最仔细、最清晰的几道python习题及答案(建议收藏哦)

名字:阿玥的小东东 学习:python。c 主页:没了 今天阿玥带大家来看看更详细的python的练习题 目录 1. 在python中, list, tuple, dict, set有什么区别, 主要应用在什么样的场景? 2. 静态函数, 类函数, 成员函数、属性函数的区别? 2.1静态…

Unix\Linux多线程复健(二)线程同步

线程同步 并非让线程并行,而是有先后的顺序执行,当有一个线程对内存操作时,其他线程不可以对这个内存地址操作 线程之间的分工合作 线程的优势之一:能够通过全局变量共享信息 临界区:访问某一共享资源的代码片段&#…

【JavaEE初阶】第六节.多线程 (基础篇 )线程安全问题(下篇)

前言 一、内存可见性 二、内存可见性的解决办法 —— volatile关键字 三、wait 和notify 关键字 3.1 wait() 方法 3.2 notify() 方法 3.3 notify All() 方法 3.4 wait 和 sleep 的对比 总结 前言 本节内容接上小节有关线程安全问题;本节内容我们将介绍有关…

CUDA编程笔记(6)

文章目录前言全局内存的访问模式合并访问和非合并访问使用全局内存进行矩阵转置矩阵复制矩阵转置总结前言 全局内存的合理使用 全局内存的访问模式 合并访问和非合并访问 合并访问指的是一个线程束(同一个线程块中相邻的wrapSize个线程。现在GPU的内建变量wrapSi…

Linux系统之网络客户端工具

Linux系统之网络客户端工具一、Links工具1.Links工具介绍2.安装Links软件3.Links工具的使用4.打印网页源码输出5.打印url版本到标准格式输出二、wget工具1.wget工具介绍2.安装wget软件3.wget工具的使用三、curl工具1.curl工具的介绍2.curl的常用参数3.curl的基本使用四、scp工具…

机器学习(二)--NumPy

本篇文章介绍了一些Numpy的基础操作。NumPy 是Python语言的一个扩充程序库。支持高级大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。📙参考:NumPy 数据类型 | 菜鸟教程 (runoob.com)1.Numpy ndarray对象Numpy最重要的一…

Introduction to Multi-Armed Bandits——04 Thompson Sampling[2]

Introduction to Multi-Armed Bandits——04 Thompson Sampling[2] 参考资料 Russo D J, Van Roy B, Kazerouni A, et al. A tutorial on thompson sampling[J]. Foundations and Trends in Machine Learning, 2018, 11(1): 1-96. ts_tutorial 项目代码地址: https://githu…

蓝桥杯刷题014——求阶乘(二分法)

求阶乘 蓝桥杯2022省赛题目 问题描述 满足 N ! 的末尾恰好有 K 个 0 的最小的 N 是多少? 如果这样的 N 不存在输出 −1 。 输入格式 一个整数 K 。 输出格式 一个整数代表答案。 样例输入 2样例输出 10评测用例规模与约定 对于 30% 的数据, 1≤K≤10^6. 对于 100% 的数据, …

新瑞鹏冲刺上市:持续亏损,旗下宠物医院屡被罚,彭永鹤为董事长

家门口的宠物医院所属集团也要上市了。 1月24日,新瑞鹏宠物医疗集团有限公司(New Ruipeng Pet Group Inc.,下称“新瑞鹏”或“新瑞鹏集团”)在美国证监会(SEC)公开提交招股书,准备在美国纳斯达…

LabVIEW什么时候需要实时系统

LabVIEW什么时候需要实时系统实时计算系统能够非常可靠地执行具有非常具体时序要求的程序,这对于许多科学和工程项目来说都很重要。构建实时系统所需的关键组件是实时操作系统(RTOS)。精确计时对于许多工程师和科学家来说,在安装了…

C 语言零基础入门教程(十)

C 作用域规则 任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量: 1、函数或块内部的局部变量 2、在所有函数外部的全局变量 3、在形式参数的函数参数定义中 让我们来看看什…

返回值的理解

前言 我们写的函数是怎么返回的,该如何返回一个临时变量,临时变量不是出栈就销毁了吗,为什么可以传递给调用方?返回对象的大小对使用的方式有影响吗?本文将带你探究这些问题,阅读本文需要对函数栈帧有一定…

Win10+GTX3060+Python+PyTorch+Tensorflow安装

本文是个备忘录,是折腾半个下午的成果,记下来免得忘记了。 0. 安装Win10,安装显卡驱动程序。 1. 弄清楚目前版本的PyTorch和Tensorflow支持哪个版本的Python。截至本文编写时,PyTorch需要Python的3.7~3.9,Tensorflow…

【NI Multisim 14.0虚拟仪器设计——放置虚拟仪器仪表(字发生器)】

目录 序言 🍍放置虚拟仪器仪表 🍉字发生器 (1)“控件”选项组 (2)“显示”选项组 (3)“触发”选项组 (4)“频率”选项组 (5)字符…

CSS 艺术之暗系魔幻卡牌

CSS 艺术之暗系魔幻卡牌参考描述效果支线HTML图片主线去除元素的部分默认属性定义 CSS 变量body#card自定义属性定义动画#card::before#card::afterimg代码总汇参考 项目描述MDNWeb 文档搜索引擎Bing 描述 项目描述Edge109.0.1518.61 (正式版本) (64 位) 效果 注:…