【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.rhkb.cn/news/281208.html

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

相关文章

旅游小程序在旅游营销中的作用及其优势

一、引言部分 随着科技的发展,移动互联网已经成为我们日常生活的一部分。对于旅游业来说,这也意味着新的机遇和挑战。其中,旅游小程序的出现为旅游业带来了全新的营销方式。本文将深入探讨旅游小程序在旅游营销中的作用以及其具体优势。 二、…

day12-SpringBootWeb 登录认证

一、登录功能 Slf4j RestController public class LoginController {Autowiredprivate EmpService empService;PostMapping("/login")public Result login(RequestBody Emp emp){log.info("员工登录: {}", emp);Emp e empService.login(emp);//登录失败, …

Java小项目--满汉楼

Java小项目–满汉楼 项目需求 项目实现 1.实现对工具包的编写 先创建libs包完成对jar包的拷贝和添加入库 德鲁伊工具包 package com.wantian.mhl.utils;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource; import java.io.FileInputStream…

C# 设置AutoScroll为true没效果的原因分析和解决办法

C#中添加tabControl 分页,将autoscroll设置为true发现缩小窗口没有滚动条效果。该问题出现后,检索发现也有很多人询问了该问题,但是都没有给出解决方案。 原因是内部button的属性Anchor设置为top、left、right、bottom导致的缩小界面窗口也没…

QT_day2:2024/3/21

作业1:使用QT完成一个登录界面 要求: 1. 需要使用Ui界面文件进行界面设计 2. ui界面上的组件相关设置,通过代码实现 3. 需要添加适当的动图 源代码: #include "widget.h" #include "ui_widget.h"Widget…

数据结构:链式二叉树

对于二叉树而言,如果不是完全二叉树,就不再适合用数组存储了 在任意二叉树中,度为0的节点都比度为2的节点多1个,即 n0 n2 1 二叉树结构 typedef struct BinTreeNode {int val;struct BinTreeNode* left;struct BinTreeNode* right; }BTNode; 二叉树…

MySQL高级学习笔记

1、MySQL架构组成 1.1 高级MySQL介绍 什么是DBA? 数据库管理员,英文是Database Administrator,简称DBA; 百度百科介绍 数据库管理员(简称DBA),是从事管理和维护数据库管理系统(D…

算法系列--递归

一.如何理解递归 递归对于初学者来说是一个非常抽象的概念,笔者在第一次学习时也是迷迷糊糊的(二叉树遍历),递归的代码看起来非常的简洁,优美,但是如何想出来递归的思路或者为什么能用递归这是初学者很难分析出来的 笔者在学习的过程中通过刷题,也总结出自己的一些经验,总结来…

【什么是Internet?网络边缘,网络核心,分组交换 vs 电路交换,接入网络和物理媒体】

文章目录 一、什么是Internet?1.从具体构成角度来看2.从服务角度来看 二、网络结构1.网络边缘1.网络边缘:采用网络设施的面向连接服务1.1.目标:在端系统之间传输数据1.2.TCP服务 2.网络边缘:采用网络设施的无连接服务2.1目标&…

探讨Java代码混淆加固工具

摘要 本篇博客将介绍几种常用的Java代码混淆工具,如ProGuard、Allatori Java Obfuscator、VirboxProtector、ipaguard和DashO。我们将深入探讨它们的特点、功能以及在保护Java应用程序安全方面的作用。此外,还将强调在使用Java代码混淆工具时需要注意的…

openssl3.2 - note - Decoders and Encoders with OpenSSL

文章目录 openssl3.2 - note - Decoders and Encoders with OpenSSL概述笔记编码器/解码器的调用链OSSL_STORE 编码器/解码器的名称和属性OSSL_FUNC_decoder_freectx_fnOSSL_FUNC_encoder_encode_fn官方文档END openssl3.2 - note - Decoders and Encoders with OpenSSL 概述 …

‍Java OCR技术全面解析:六大解决方案比较

博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …

[ C++ ] STL---stack与queue

目录 stack简介 stack的常用接口 queue简介 queue的常用接口 stack的模拟实现 queue的模拟实现 stack简介 1. stack是具有后进先出操作的一种容器适配器,其只能从容器的一端进行元素的插入与删除操作; 2. stack是作为容器适配器被实现的&#xff0…

ubuntu20.04搭建rtmp视频服务

1.安装软件 sudo apt-get install ffmpeg sudo apt-get install nginx sudo apt-get install libnginx-mod-rtmp 2.nginx配置 修改/etc/nginx/nginx.conf文件,在末尾添加: rtmp {server {listen 1935;application live {live on;}} } 3.视频测试 本…

使用 ZipArchiveInputStream 读取压缩包内文件总数

读取压缩包内文件总数 简介 ZipArchiveInputStream 是 Apache Commons Compress 库中的一个类,用于读取 ZIP 格式的压缩文件。在处理 ZIP 文件时,编码格式是一个重要的问题,因为它决定了如何解释文件中的字符数据。通常情况下,Z…

权限管理系统-0.5.0

六、审批管理模块 审批管理模块包括审批类型和审批模板&#xff0c;审批类型如&#xff1a;出勤、人事、财务等&#xff0c;审批模板如&#xff1a;加班、请假等具体业务。 6.1 引入依赖 在项目中引入activiti7的相关依赖&#xff1a; <!--引入activiti的springboot启动器…

TikTok美国本土小店如何运营?常见小白问题解答

作为资深跨境老玩家&#xff0c;虽不说是经验丰富&#xff0c;至少也是摸清了基本的玩法思路。TikTok作为近来的跨境新蓝海&#xff0c;他的玩法其实并不难&#xff0c;作为第一批试错玩家&#xff0c;今天也诚心给大家分享一些美国本土小店运营经验&#xff0c;感兴趣的话就看…

当我们谈论Spring的时候到底在谈什么

题图来自APOD 你好&#xff0c;这里是codetrend专栏“Spring6全攻略”。欢迎点击关注查看往期文章。 Spring对于不做程序开发的人来说字面意思就是春天&#xff0c;四季的开始。 对于程序员来说这个单词完全拥有另外一个含义&#xff0c;Spring指的是一个开源项目&#xff0…

C语言经典算法-6

文章目录 其他经典例题跳转链接31.数字拆解32.得分排行33.选择、插入、气泡排序34.Shell 排序法 - 改良的插入排序35.Shaker 排序法 - 改良的气泡排序 其他经典例题跳转链接 C语言经典算法-1 1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠走迷官&#xff08;一&…

Python图像处理指南:PIL与OpenCV的比较【第136篇—PIL】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python图像处理指南&#xff1a;PIL与OpenCV的比较 图像处理在计算机视觉和图像识别等领域…