NodeJS Web 框架 Express 之中间件

news2024/10/7 16:26:51

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 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/179282.html

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

相关文章

从0-1开始 测试ZLMediaKit推拉流性能、延时性能

流媒体开发系列文章 文章目录流媒体开发系列文章前言一、环境准备?二、拉流测试过程三、推流测试过程三、延时测试总结前言 目前、比较有名的流媒体服务器有ZLMediaKit、srs、live555、eadydarwin等。因为srs是单线程服务、对于多核服务器的支持需要通过部署多个服…

pytorch深度学习基础(十一)——常用结构化CNN模型构建

结构化CNN模型构建与测试前言GoogLeNet结构Inception块模型构建resNet18模型结构残差块模型构建denseNet模型结构DenseBlocktransition_block模型构建结尾前言 在本专栏的上一篇博客中我们介绍了常用的线性模型,在本文中我们将介绍GoogleNet、resNet、denseNet这类…

APT之木马静态免杀

前言 这篇文章主要是记录手动编写代码进行木马免杀,使用工具也可以免杀,只不过太脚本小子了,而且工具的特征也容易被杀软抓到,指不定哪天就用不了了,所以要学一下手动去免杀木马,也方便以后开发一个只属于…

blender导入骨骼动画方法[psa动作]

先导入女性的psk文件 然后调整缩放大小和人物一样,包括角度朝向. ctrla应用所有改变 然后选择psk文件以及其他人物模型的全部 ,然后 在Layout-物体-父级 -附带空顶相点组 image.png之后会发现所有人物多了修改器,点击其中一个修改器 点添加修改器 -数据传递 勾选顶点数据-选择顶…

人员动作行为AI分析系统 yolov5

人员动作行为AI分析系统通过pythonyolo系列网络学习模型,对现场画面人员行为进行实时分析监测,自动识别出人的各种异常行为动作,立即抓拍存档预警同步回传给后台。 我们使用YOLO算法进行对象检测。YOLO是一个聪明的卷积神经网络(CNN)&#xf…

带滤波器的PID控制仿真-1

采用低通滤波器可有效地滤掉噪声信号,在控制系统的设计中是一种常用的方法。基于低通滤波器的信号处理实例设低通滤波器为:采样时间为1ms,输入信号为带有高频正弦噪声( 100Hz)的低频(0.2Hz)正弦信号。采用低…

离散数学与组合数学-05树

文章目录离散数学与组合数学-05树5.1 认识树5.1.1 树的模型5.1.2 树的应用5.2 无向树5.2.1 定义5.2.2 树的性质5.2.3 性质应用5.3 生成树5.3.1 引入5.3.2 定义5.3.3 算法5.3.4 应用5.4 最小生成树5.4.1 引入5.4.2 定义5.4.3 算法5.5 根树5.5.1 根数定义5.5.2 倒置法5.5.3 树的家…

【编程入门】开源记事本(SwiftUI版)

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

C++模板进阶

这篇文章是对模板初阶的一些补充,让大家在进行深一层的理解。 文章目录1. 非类型模板参数2. 模板的特化2.1 概念2.2 函数模板特化2.3 类模板特化2.3.1 全特化2.3.2 偏特化2.4 类模板特化应用示例3 模板分离编译3.1 什么是分离编译3.2 模板的分离编译3.3 解决方法4.…

【各种**问题系列】什么是 LTS 长期支持

目录 🍁 什么是长期支持(LTS)版本? 🍂 LTS 版本的优点: 🍁 什么是 Ubuntu LTS? 🍂 Ubuntu LTS 软件更新包括什么? 在 Linux 的世界里,特别是谈…

【Java开发】Spring Cloud 08 :链路追踪

任何一个架构难免会出现bug,微服务相比于单体架构日志查询更为困难,因此spring cloud推出了Sleuth等组件的链路追踪技术来实现报错信息的定位及查询。项目源码:尹煜 / coupon-yinyu GitCode1 调用链追踪我们可以想象这样一个场景&#xff0c…

单一数字评估指标、迁移学习、多任务学习、端到端的深度学习

目录1.单一数字评估指标(a single number evaluation metric)有时候要比较那个分类器更好,或者哪个模型更好,有很多指标,很难抉择,这个时候就需要设置一个单一数字评估指标。例1:比较A,B两个分类器的性能&a…

Android MVVM的实现

Android MVVM的实现 前言: 在我们写一些项目的时候,通常会对一些常用的一些常用功能进行抽象封装,简单例子:比如BaseActivity,BaseFragment等等…一般这些Base会去承载一些比如标题栏,主题之类的工作&…

提权漏洞和域渗透历史漏洞整理

Windows提权在线辅助工具 https://i.hacking8.com/tiquan/🌴Kernel privilege escalation vulnerability collection, with compilation environment, demo GIF map, vulnerability details, executable file (提权漏洞合集) https://github.com/Ascotbe/Kernelhu…

恶意代码分析实战 13 反调试技术

13.1 Lab16-01 首先,将可执行文件拖入IDA中。 我们可以看到有三处都调用了sub_401000函数,并且代码都在哪里停止执行。由于没有一条线从这些方框中引出,这就意味着函数可能终止了程序。 右侧每一个大框中都包含一个检查,这个检查…

Makefile学习②:Makefile基本语法

Makefile学习②:Makefile基本语法 Makefile基本语法 目标: 依赖 (Tab)命令 目标:一般是指要编译的目标,也可以是一个动作 依赖:指执行当前目标所要依赖的先项,包括其他目标&#xf…

neural collaborative filtering 阅读笔记

本文主要介绍了一种一种基于神经网络的技术,来解决在含有隐形反馈的基础上进行推荐的关键问题————协同过滤。 2.1 Learning from Implicit Data yui1,(ifinteraction(useru,itemi)isobserved)y_{ui} 1,(if interaction (user u, item i) is observed)yui​1,(…

还在为ElementUI的原生校验方式苦恼吗,快用享受element-ui-verify插件的快乐吧(待续)

element-ui-verify 本文章意在介绍element-ui-verify插件使用&#xff0c;以及对比elementUI原生校验方式&#xff0c;突显该插件用少量代码也能实现原生的校验效果甚至更好。 1.先观察一个示例 <template><d2-container><el-form :model"ruleForm&qu…

二叉树超级经典OJ题

目录1.根据二叉树创建字符串2.二叉树的层序遍历3.二叉树的层序遍历II4.二叉树的最近公共祖先5.二叉搜索树与双向链表6.从前序与中序遍历序列构造二叉树1.根据二叉树创建字符串 根据二叉树创建字符串 给你二叉树的根节点root&#xff0c;请你采用前序遍历的方式&#xff0c;将二…

编码器M法测速仿真(Simulink)

编码器M法和T法测速的详细讲解可以参看下面的文章链接,这里不再赘述,这里主要介绍Simulink里建模仿真,带大家从另一个角度理解编码器测速原理。 PLC通过编码器反馈值计算速度的推荐做法(算法解析+ST代码)_RXXW_Dor的博客-CSDN博客_编码器计算速度程序实例PLC如何测量采集编…