【Node.js从基础到高级运用】十三、NodeJS中间件高级应用

在现代web开发中,Node.js因其高效和灵活性而备受青睐。其中,中间件的概念是构建高效Node.js应用的关键。在这篇博客文章中,我们将深入探讨Node.js中间件的高级应用,包括创建自定义中间件、使用第三方中间件等。我们将从基础讲起,逐步深入,旨在为读者提供全面而深入的指南。

中间件简介

在Node.js中,中间件是一个函数,它可以访问请求对象(req)、响应对象(res)和应用程序的请求/响应循环中的下一个中间件函数。这些函数可以执行以下任务:

  • 执行任何代码。
  • 修改请求和响应对象。
  • 结束请求/响应循环。
  • 调用堆栈中的下一个中间件函数。

如果当前中间件函数没有结束请求/响应循环,它必须调用next()方法将控制权传递给下一个中间件函数,否则请求将被挂起。

中间件的类型

在 Node.js 中,根据用途和功能的不同,中间件大体可以分为以下几类:

  • 应用级中间件: 通过使用 app.use()app.METHOD() 函数加载,并且可以执行任何代码、修改请求和响应对象、结束请求-响应循环、调用下一个中间件。

  • 路由级中间件: 和应用级中间件相似,但它绑定到一个实例上 express.Router()。

  • 错误处理中间件: 用来处理应用中发生的各种错误。这类中间件通常定义了四个参数 (err, req, res, next)

  • 内置中间件: Express 框架自带的中间件,例如 express.static,用于提供静态资源。

  • 第三方中间件: 由社区开发,需要通过 npm 安装,可以提供额外的功能,比如解析请求体、处理 cookie 等。

创建自定义中间件

自定义中间件是扩展Express应用功能的基石。它允许我们对进入的请求进行预处理、实施安全检查、处理日志等。

示例:追踪请求时间

// 追踪请求处理时间的中间件
function requestTimeLogger(req, res, next) {const start = Date.now(); // 请求开始时间res.on('finish', () => { // 响应结束时触发const duration = Date.now() - start; // 计算处理时长console.log(`${req.method} ${req.url} - ${duration}ms`); // 记录请求方法、URL和处理时长});next(); // 继续处理请求
}app.use(requestTimeLogger); // 将中间件添加到应用中

运行结果:
结合之前学过的books demo完成测试:
在这里插入图片描述

使用express.static提供静态文件服务

const express = require('express');
app.use(express.static('public')); // `public`目录下的文件现在可以通过Web访问了

创建public文件夹,在该文件夹下创建index.html文件,打开本地服务默认为index页面

使用第三方中间件

第三方中间件大大简化了常见功能的实现,如体解析、Cookie处理等。

示例:使用cors中间件管理跨源请求

const cors = require('cors'); // 引入cors中间件app.use(cors()); // 应用cors中间件,允许所有跨域请求

这行代码使得我们的Node.js应用可以接受跨域请求,极大地简化了配置过程

响应格式化中间件

// 响应格式化中间件
function responseFormatter(req, res, next) {res.apiResponse = (data, error = null) => {if (error) {res.status(500).json({ success: false, error }); // 发送错误响应} else {res.status(200).json({ success: true, data }); // 发送成功响应}};next();
}app.use(responseFormatter);

中间件组合应用

请求时间记录和请求大小限制

这个例子中,我们将使用两个自定义中间件,一个用于记录请求时间,另一个用于限制请求体的大小。

const express = require('express');
const bodyParser = require('body-parser');const app = express();// 中间件1: 记录请求时间
app.use((req, res, next) => {req.requestTime = Date.now(); // 添加一个新属性来保存请求时间next(); // 调用下一个中间件
});// 中间件2: 限制请求体的大小
app.use(bodyParser.json({ limit: '10kb' })); // 使用body-parser限制请求体大小为10kb// 路由处理
app.get('/', (req, res) => {const responseText = `Requested at: ${req.requestTime}`; // 使用中间件1添加的属性res.send(responseText);
});
// 测试限制请求体大小
app.post('/', (req, res) => {res.send('Received your request!');
});
// 添加错误处理中间件来观察限制
app.use((err, req, res, next) => {if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {// 当请求体过大时,body-parser会抛出一个错误return res.status(413).send({ message: 'Request entity too large' });}// 如果不是请求体大小的问题,继续传递错误next(err);
});
// 启动服务器
app.listen(3000, () => {console.log('Server is running on port 3000');
});

运行结果:
在这里插入图片描述

条件性中间件和资源压缩

在这个例子中,我们将创建一个条件性中间件,用于根据请求的路径决定是否压缩响应。我们还会使用compression中间件来进行资源压缩。

const express = require('express');
const compression = require('compression');const app = express();// 条件性中间件: 只有请求路径为"/compress"时才使用compression中间件
app.use((req, res, next) => {if (req.path === '/compress') {compression()(req, res, next); // 调用compression中间件} else {next(); // 不需要压缩,直接调用下一个中间件}
});// 路由处理
app.get('/compress', (req, res) => {// 发送一个大文本,压缩后传输const largeText = '...'; // 假设这里是一个很大的文本内容res.send(largeText);
});app.get('/no-compress', (req, res) => {// 发送一个文本,不进行压缩res.send('This response is not compressed.');
});// 启动服务器
app.listen(3000, () => {console.log('Server is running on port 3000 with conditional compression');
});

我们要做的是向/compress路由发送一个GET请求,并观察返回的响应头信息。检查响应头中的Content-Encoding是否包含gzip,这表明响应已经被压缩。

运行结果:
响应数据太小,没有达到压缩的阈值:
在这里插入图片描述

足够大的数据响应:

// 路由处理
app.get('/compress', (req, res) => {// 发送一个大文本,压缩后传输const largeText = '这是一个重复的大文本字符串,用于生成足够大的响应体来触发压缩。'.repeat(1000);res.send(largeText);
});

在这里插入图片描述

API速率限制和缓存

在这个例子中,我们将使用express-rate-limit来限制API的调用速率,以及使用简单的内存缓存来存储响应数据,减少重复计算。

mcache.putmemory-cache库中的一个方法,用于将数据存储在内存中。它接受三个参数:
key(字符串):用于存储和检索缓存值的唯一标识符。
value(任意类型):要存储在缓存中的数据。
time(毫秒):缓存数据在内存中存储的时间。超过这个时间后,数据将从缓存中删除。

const express = require('express');
const rateLimit = require('express-rate-limit');
const mcache = require('memory-cache');const app = express();// API速率限制中间件
const limiter = rateLimit({windowMs: 15 * 60 * 1000, // 15分钟max: 100 // 在15分钟内每个IP最多100次请求
});// 应用API速率限制中间件
app.use('/api', limiter);// 缓存中间件
const cache = (duration) => {return (req, res, next) => {let key = '__express__' + req.originalUrl || req.url;let cachedBody = mcache.get(key);if (cachedBody) {res.send(cachedBody);return;} else {res.sendResponse = res.send;res.send = (body) => {mcache.put(key, body, duration * 1000);// 将响应体存储在缓存中,duration以秒为单位res.sendResponse(body);};next();}};
};// 路由处理
app.get('/api/expensive-data', cache(30), (req, res) => {// 假设这里有一些计算成本很高的数据生成过程const expensiveData = 'Expensive Data';res.send(expensiveData);
});// 启动服务器
app.listen(3000, () => {console.log('Server is running on port 3000 with rate limiting and caching');
});
限制API调用速率

为了测试例子3中限制API调用速率的功能,我们可以快速连续发送多个请求到服务器的/api路由。如果超过了限制的数量,服务器应该返回429状态码(Too Many Requests)。
使用curl命令来测试API速率限制:

for i in {1..101}; do curl -X GET http://localhost:3000/api/expensive-data; done

在这里插入图片描述
这个命令会连续发送101个GET请求到/api/expensive-data。由于我们设置了15分钟内每个IP最多100次请求,所以在第101次请求时,将会收到Too many requests, please try again later.

测试内存缓存

为了测试内存缓存的功能,我们可以发送两次请求到同一个路由,并观察响应时间。第一次请求会生成数据并将其缓存,而第二次请求应该会直接从缓存中获取数据,响应时间应该更短。

app.get('/api/cache-data', cache(30), (req, res) => {// 假设这里有一些计算成本很高的数据生成过程const largeText = '这是一个重复的大文本字符串,用于生成足够大的响应体来触发压缩。'.repeat(1000);res.send(largeText);
});

使用curl命令来测试内存缓存:

# 第一次请求
time curl -X GET http://localhost:3000/api/cache-data# 等待几秒钟# 第二次请求
time curl -X GET http://localhost:3000/api/cache-data

time命令会显示curl命令执行所需的时间。第二次请求应该比第一次快,因为它应该是从缓存中获取数据。
第一次:
在这里插入图片描述
第二次:
在这里插入图片描述

总结

在上述例子中,我们展示了如何使用Node.js中间件进行请求时间记录、请求大小限制、条件性资源压缩、API速率限制以及简单的内存缓存。这些中间件的组合运用能够帮助你构建更加健壮和高效的Node.js应用程序。

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

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

相关文章

AJAX-原理XMLHttpRequest

定义 使用 查询参数 定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据 语法:http://xxxx.com/xxx/xxx?参数名1值1&参数名2值2

ChatGPT编程Python小案例(拿来就用)—解压zip压缩文件

ChatGPT编程Python小案例(拿来就用)—解压zip压缩文件 今天撸一本书,其中书中提供一个zip压缩文件的资料。下载之后,没有解压软件,(也可能该文件可以自解压)。这段时间已经深刻体会到AI编程带来…

爬虫 Day2

resp.close()#关掉resp 一requests入门 (一) 用到的网页:豆瓣电影分类排行榜 - 喜剧片 import requestsurl "https://movie.douban.com/j/chart/top_list" #参数太长,重新封装参数 param {"type": "…

【Unity每日一记】unity中的内置宏和条件编译(Unity内置脚本符号)

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

EDI在汽车主机厂配送流程中的应用

汽车主机厂的汽车配送流程始于汽车 “生产结束 ” ,止于 “交付给经销商 ” 。在这个流程中,企业作为主机厂的下游供应商,与主机厂的物流服务供应商之间的信息交换将会变得十分重要。 配送流程:运输订单以及报告 汽车主机厂提供预…

【linux驱动】定时器的使用

【linux驱动】定时器的使用 文章目录 【linux驱动】定时器的使用1.介绍1.1相关名词1.2配置HZ的方法 2.API3.示例4.调试 1.介绍 1.1相关名词 HZ、jiffies、tick Linux系统启动后,每隔固定周期就会发出timer interrupt(IRQ 0),HZ用来定义每一秒发生多少…

Day50| 123 买卖股票的最佳时机III 188 买卖股票的最佳时机IV

目录 123 买卖股票的最佳时机III 188 买卖股票的最佳时机IV 123 买卖股票的最佳时机III class Solution { public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size() 1, vector<int>(5, 0));dp[0][0] 0;dp[0][…

模块化开发在不同编程语言中的实现方式有何异同?并以LabVIEW为例进行说明

模块化开发是一种软件设计方法&#xff0c;它将一个大型程序分解成独立的、可以单独开发和测试的模块或组件。这种方法提高了代码的可重用性、可维护性和可测试性。不同编程语言实现模块化开发的方式各有特色&#xff0c;但都遵循基本的设计原则&#xff0c;如封装、接口抽象和…

【机器学习】经典目标检测算法:RCNN、Fast RCNN、 Faster RCNN 基本思想和网络结构介绍

文章目录 三者的比较&#xff1a;RCNN、Fast RCNN、 Faster RCNN一、框架的对比1.三者都是二阶算法&#xff0c;网络框架比较&#xff1a;2.三者的优缺点比较&#xff1a; RCNN一、RCNN系列简介二、RCNN算法流程的4个步骤三、RCNN存在的问题四、论文解析补充1.R-CNN提出了两个问…

Odoo17免费开源ERP开发技巧:如何在表单视图中调用JS类

文/Odoo亚太金牌服务开源智造 老杨 在Odoo最新V17新版中&#xff0c;其突出功能之一是能够构建个性化视图&#xff0c;允许用户以独特的方式与数据互动。本文深入探讨了如何使用 JavaScript 类来呈现表单视图来创建自定义视图。通过学习本教程&#xff0c;你将获得关于开发Odo…

【ceph】配置 ceph dashboard 详细配置过程

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

C++ QT串口通信(1)-串口模块QtSerialPort详解

本文讲解C++ QT串口模块QtSerialPort。 目录 一、串口通信基础与QtSerialPort模块简介 1.1 串口通信基础 1.2 QtSerialPort模块简介

环境变量和Bash内置命令

Command Line Editing Ctrla#Move to the start of the line.(光标移到最前面) Ctrle#Move to the end of the line.(光标移到最后面) Ctrll#Clear the screen, reprinting the current line at the top.(不等同clear命令.会在顶部重新打印当前行,当前行还有内容时,还会显示) …

放慢音频速度的三个方法 享受慢音乐

如何让音频慢速播放&#xff1f;我们都知道&#xff0c;在观看视频时&#xff0c;我们可以选择快进播放&#xff0c;但是很少有软件支持慢速播放。然而&#xff0c;将音频慢速播放在某些情况下是非常必要的。例如&#xff0c;当我们学习一门新语言时&#xff0c;我们可以将音频…

Pytorch详细应用基础(全)

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 1.安装pytorch以及anaconda配置 尽量保持默认的通道&#xff0c;每次写指令把镜像地址写上就行。 defaults优先级是最低的&#…

动态代理IP在反爬虫策略中的实战运用与挑战

动态代理IP在反爬虫策略中的实战运用与挑战是现代网络数据抓取领域中一个核心议题。动态代理IP服务允许爬虫程序通过不断切换不同的IP地址来访问目标网站&#xff0c;以应对各种反爬虫技术措施&#xff0c;主要包括以下几点&#xff1a; 实战运用&#xff1a; 1. 绕过IP限制&a…

React——关于事件处理

如何注册事件 驼峰命名法&#xff0c;语法on事件名&#xff5b;事件处理程序&#xff5d; 比如onClick{this.buttonClick} class App extends React.Component {render() {return (<div><button onClick{this.buttonClick}>点击按钮触发事件</button></di…

深度学习神经网络相关记录《二》

如何判断模型是一个好模型&#xff1f; 模型预测效果&#xff0c;也就是模型预测的准确率运算速度&#xff1b;能够处理大量数据、短时间内急速学习、可以实时进行预测&#xff0c;是机器学习的重要优势&#xff1b;可解释性&#xff1b;深度学习已经不太关系这一点了&#xf…

macOS安装erlang以及rabbitMq详情版本

1.利用HomeBrew安装&#xff0c;如果你电脑没有HomeBrew可以跳转到HomeBrew安装教程 https://blog.csdn.net/weixin_50268501/article/details/136820299 2.要想运行rabbitMq要有Erlang 安装erlang brew install erlang3.安装RabbitMq brew install rabbitmq4.执行完上述命令…

基础:TCP三次握手做了什么,为什么要握手?

1. TCP 三次握手在做些什么 1. 第一次握手 &#xff1a; 1&#xff09;握手作用&#xff1a;客户端发出建立连接请求。 2&#xff09;数据处理&#xff1a;客户端发送连接请求报文段&#xff0c;将SYN位置为1&#xff0c;Sequence Number为x;然后&#xff0c;客户端进入SYN_S…