Express中间件(Middleware)详解:从零开始掌握(1)

news2025/5/17 14:22:44

1. 中间件是什么?

想象中间件就像一个"加工流水线",请求(Request)从进入服务器到返回响应(Response)的过程中,会经过一个个"工作站"进行处理。

简单定义:中间件是能够访问请求对象(req)、响应对象(res)和下一个中间件函数(next)的函数。

2. 基本结构

一个最基本的中间件函数长这样:

function myMiddleware(req, res, next) {
  // 对req或res做一些处理
  console.log('这是中间件');
  next(); // 告诉Express转到下一个中间件
}

3. 中间件的工作原理

Express应用的请求处理流程就像这样:

请求 → 
中间件1 → 
中间件2 → 
... → 
路由处理器 → 
响应

每个中间件可以:

  • 执行任何代码
  • 修改请求和响应对象
  • 结束请求-响应循环
  • 调用下一个中间件

类型:

类型示例特点
应用级app.use()对所有请求生效
路由级router.use()仅对特定路由生效
错误处理app.use((err, req, res, next))必须4个参数
内置express.json()Express提供
第三方body-parser社区贡献

关键点

  • 每个中间件通过调用next()将控制权传递给下一个
  • 如果不调用next(),请求处理会终止
  • 错误处理中间件需要4个参数:(err, req, res, next)

4. 实际例子:记录请求日志

const express = require('express');
const app = express();

// 自定义日志中间件
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next(); // 必须调用next()才能继续
});

app.get('/', (req, res) => {
  res.send('首页');
});

app.listen(3000);

看下日志:

5. 中间件类型

应用级中间件

// 对所有路由生效
app.use(myMiddleware);

// 对特定路由生效
app.use('/admin', adminMiddleware);

路由级中间件

router.use(middlewareFunction);

内置中间件

app.use(express.json()); // 解析JSON请求体
app.use(express.urlencoded({ extended: true })); // 解析表单数据
app.use(express.static('public')); // 静态文件服务

错误处理中间件

// 注意有4个参数
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('出错了!');
});

第三方中间件

const morgan = require('morgan');
app.use(morgan('dev')); // 日志中间件

6. 中间件执行顺序练习

app.use((req, res, next) => {
  console.log('第一');
  next();
});

app.use((req, res, next) => {
  console.log('第二');
  next();
});

app.get('/', (req, res) => {
  console.log('最后');
  res.send('完成');
});

7. 重要原则

  1. 顺序很重要:中间件按添加顺序执行
  2. 必须调用next():除非你想终止流程
  3. 错误处理中间件:必须放在最后
  4. 可以修改req/res:后面的中间件会看到修改

8. 练习

        8.1. 记录请求耗时的中间件

const express = require('express');
const app = express();

// 记录请求耗时的中间件
app.use((req, res, next) => {
  const startTime = Date.now(); // 记录开始时间
  
  // 在响应结束时计算耗时
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    console.log(`${req.method} ${req.url} - ${duration}ms`);
  });
  
  next(); // 继续处理请求
});

// 测试路由
app.get('/', (req, res) => {
  // 模拟耗时操作
  setTimeout(() => {
    res.send('首页');
  }, 300);
});

app.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});

          8.2. 记录请求耗时的中间件

//  验证API密钥的中间件
const express = require('express');
const app = express();

// API密钥验证中间件
const apiKeyValidator = (req, res, next) => {
  const validApiKeys = ['123-abc', '456-def', '789-ghi'];
  const apiKey = req.headers['x-api-key'] || req.query.apiKey;
  
  if (!apiKey) {
    return res.status(401).json({ error: '缺少API密钥' });
  }
  
  if (!validApiKeys.includes(apiKey)) {
    return res.status(403).json({ error: '无效的API密钥' });
  }
  
  console.log(`API密钥验证通过: ${apiKey}`);
  next(); // 验证通过
};

// 受保护的路由
app.get('/protected', apiKeyValidator, (req, res) => {
  res.json({ message: '你已访问受保护的内容' });
});

// 测试方式:
// 1. curl http://localhost:3000/protected?apiKey=123-abc
// 2. curl -H "x-api-key: 123-abc" http://localhost:3000/protected

app.listen(3000);

        8.3. 组合多个中间件

const express = require('express');
const app = express();

// 中间件1: 记录请求信息
const requestLogger = (req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.ip} ${req.method} ${req.path}`);
  next();
};

// 中间件2: 验证用户身份
const userAuth = (req, res, next) => {
  // 模拟用户验证
  const isAuthenticated = Math.random() > 0.3; // 70%概率通过
  
  if (isAuthenticated) {
    req.user = { id: 123, name: '示例用户' }; // 附加用户信息到请求对象
    next();
  } else {
    res.status(401).send('请先登录');
  }
};

// 中间件3: 检查管理员权限
const adminCheck = (req, res, next) => {
  if (req.user && req.user.isAdmin) {
    next();
  } else {
    res.status(403).send('需要管理员权限');
  }
};

// 组合使用多个中间件
app.get('/profile', 
  requestLogger, 
  userAuth, 
  (req, res) => {
    res.send(`欢迎, ${req.user.name}`);
  }
);

app.get('/admin', 
  requestLogger, 
  userAuth, 
  adminCheck, 
  (req, res) => {
    res.send('管理员面板');
  }
);

// 测试路由
app.get('/public', requestLogger, (req, res) => {
  res.send('公开内容');
});

app.listen(3000);

本节到这里就结束了,下节将讲解进阶版中间件。

Express中间件(Middleware)详解:从零开始掌握(2)-CSDN博客

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

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

相关文章

现代工业测试的核心支柱:电机试验工作台?(北重机械厂家)

电机试验工作台是现代工业测试中的核心支柱之一。这种工作台通常用于对各种类型的电机进行性能测试、负载测试和耐久性测试。通过电机试验工作台,工程师可以评估电机的效率、功率输出、转速、扭矩、温度等关键参数,从而确保电机的设计符合要求&#xff0…

oracle 11g密码长度和复杂度查看与设置

verify_function_11G 的密码复杂性要求: 密码长度至少为 8 个字符。 密码必须包含至少一个数字和一个字母字符。 密码不能与用户名相同或相似。 密码不能是服务器名或其变体。 密码不能是常见的弱密码(如 welcome1、oracle123 等)。 注意事项&…

CVE-2025-32375 | Windows下复现 BentoML runner 服务器远程命令执行漏洞

目录 1. 漏洞描述2. 漏洞复现1. 安装 BentoML 1.4.72. 创建模型3. 构建模型4. 托管模型5. 执行exp 3. POC4. 补充学习 参考链接: https://mp.weixin.qq.com/s/IxLZr83RvYqfZ_eXhtNvgg https://github.com/bentoml/BentoML/security/advisories/GHSA-7v4r-c989-xh26 …

某局jsvmp算法分析(dunshan.js/lzkqow23819/lzkqow39189)

帮朋友看一个税某局的加密算法。 传送门 (需要帐号登陆的 普通人没授权也看不了) 废话不多说直接抓包开干 这里可以看到一个headers中的加密参数 lzkqow23819 以及url路径里面的6eMrZlPH(这个有点像瑞数里面的) 还有就是cookies里面的这几个…

AlmaLinux9.5 修改为静态IP地址

查看当前需要修改的网卡名称 ip a进入网卡目录 cd /etc/NetworkManager/system-connections找到对应网卡配置文件进行修改 修改配置 主要修改ipv4部分,改成自己的IP配置 [ipv4] methodmanual address1192.168.252.129/24,192.168.252.254 dns8.8.8.8重启网卡 …

操作系统 4.4-从生磁盘到文件

文件介绍 操作系统中对磁盘使用的第三层抽象——文件。这一层抽象建立在盘块(block)和文件(file)之间,使得用户可以以更直观和易于理解的方式与磁盘交互,而无需直接处理磁盘的物理细节如扇区(se…

免费多语言文档翻译软件推荐

软件介绍 今天给大家介绍一款文档翻译助手。它能够支持PDF、Word等多种文档格式,涵盖中文、英文、日语等多语言互译。此软件在翻译过程中精选保留文档原貌,每段文字、每个图表的匹配都十分完美,还依托顶尖翻译大模型,让翻译结果符…

安全序列(DP)

#include <bits/stdc.h> using namespace std; const int MOD1e97; const int N1e65; int f[N]; int main() {int n,k;cin>>n>>k;f[0]1;for(int i1;i<n;i){f[i]f[i-1]; // 不放桶&#xff1a;延续前一位的所有方案if(i-k-1>0){f[i](f[i]f[i-k…

【Flask开发】嘿马文学web完整flask项目第4篇:4.分类,4.分类【附代码文档】

教程总体简介&#xff1a;2. 目标 1.1产品与开发 1.2环境配置 1.3 运行方式 1.4目录说明 1.5数据库设计 2.用户认证 Json Web Token(JWT) 3.书架 4.1分类列表 5.搜索 5.3搜索-精准&高匹配&推荐 6.小说 6.4推荐-同类热门推荐 7.浏览记录 8.1配置-阅读偏好 8.配置 9.1项目…

SQL开发的智能助手:通义灵码在IntelliJ IDEA中的应用

SQL 是一种至关重要的数据库操作语言&#xff0c;尽管其语法与通用编程语言有所不同&#xff0c;但因其在众多应用中的广泛使用&#xff0c;大多数程序员都具备一定的 SQL 编写能力。然而&#xff0c;当面对复杂的 SQL 语句或优化需求时&#xff0c;往往需要专业数据库开发工程…

解决:AttributeError: module ‘cv2‘ has no attribute ‘COLOR_BGR2RGB‘

opencv AttributeError: module ‘cv2’ has no attribute ‘warpFrame’ 或者 opencv 没有 rgbd 解决上述问题的方法是&#xff1a; 卸载重装。 但是一定要卸载干净&#xff0c;仅仅卸载opencv-python是不行的。无限重复都报这个错。 使用pip list | grep opencv查看相关的…

NutriJarvis:AI慧眼识餐,精准营养触手可及!—— 基于深度学习的菜品识别与营养计算系统

NutriJarvis&#xff1a;AI慧眼识餐&#xff0c;精准营养触手可及&#xff01;—— 基于深度学习的菜品识别与营养计算系统 NutriJarvis 是一个基于深度学习的菜品识别与营养计算系统&#xff0c;旨在通过计算机视觉技术自动识别餐盘中的食物&#xff0c;并估算其营养成分&…

【LaTeX】

基本使用 \documentclass 类型&#xff1a;文章&#xff08;article&#xff09;、报告&#xff08;report&#xff09;、书&#xff08;book&#xff09; 中文的文章是ctexart&#xff0c;中文字体是UTF8 \documentclass[UTF8]{ctexart} []说明可以省略不写的意思&#xf…

细说STM32单片机FreeRTOS任务管理相关函数及多任务编程的实现方法

目录 一、FreeRTOS任务管理相关函数 1、FreeRTOS函数 2、FreeRTOS宏函数 3、主要函数功能说明 &#xff08;1&#xff09;创建任务osThreadNew() &#xff08;2&#xff09;删除任务vTaskDelete() &#xff08;3&#xff09;挂起任务vTaskSuspend() &#xff08;4&…

uniapp微信小程序基于wu-input二次封装TInput组件(支持点击下拉选择、支持整数、电话、小数、身份证、小数点位数控制功能)

一、 最终效果 二、实现了功能 1、支持输入正整数---设置specifyTypeinteger 2、支持输入数字&#xff08;含小数点&#xff09;---设置specifyTypedecimal&#xff0c;可设置decimalLimit来调整小数点位数 3、支持输入手机号--设置specifyTypephone 4、支持输入身份证号---设…

leetcode-419.棋盘上的战舰

leetcode-419.棋盘上的战舰 文章目录 leetcode-419.棋盘上的战舰一.题目描述二.第一次代码提交三.第二次代码提交 一.题目描述 二.第一次代码提交 class Solution { public:int countBattleships(vector<vector<char>>& board) {int m board.size(); //列数i…

使用uglifyjs对静态引入的js文件进行压缩

前言 因为有时候js文件没有npm包&#xff0c;或者需要修改&#xff0c;只能引入静态的js&#xff0c;那么这个时候就可以对js进行压缩了。我其实想通过vite、webpack等插件进行压缩的&#xff0c;可是他都不能定位到public目录下面的文件&#xff0c;所以我只能自己压缩了。编…

程序加壳脱壳原理和实现

理论 一个可运行的执行文件&#xff0c;至少会有一个代码段&#xff0c;程序的入口点指向代码段&#xff0c;程序运行的时候&#xff0c;从入口点开始执行代码段指令 为了将一个正常的程序进行加壳保护&#xff0c;至少要三部分逻辑配合 1、待加壳保护的程序 2、加壳逻辑 3…

【数据分析实战】使用 Matplotlib 绘制折线图

1、简述 在日常的数据分析、科研报告、项目可视化展示中&#xff0c;折线图是一种非常常见且直观的数据可视化方式。本文将带你快速上手 Matplotlib&#xff0c;并通过几个实际例子掌握折线图的绘制方法。 Matplotlib 是 Python 中最常用的数据可视化库之一&#xff0c;它能够…

数据仓库标准库模型架构相关概念浅讲

数据仓库与模型体系及相关概念 数据仓库与数据库的区别可参考&#xff1a;数据库与数据仓库的区别及关系_数据仓库和数据库-CSDN博客 总之&#xff0c;数据库是为捕获数据而设计&#xff0c;数据仓库是为分析数据而设计 数据仓库集成工具 在一些大厂中&#xff0c;其会有自…