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

news2025/4/18 9:36:42

1. 请求耗时中间件的增强版

问题:原版只能记录到控制台,如何记录到文件?

改进点

  1. 使用process.hrtime()是什么?获取更高精度的时间
  2. 支持将日志写入文件
  3. 记录更多信息(IP地址、状态码)
  4. 工厂函数模式使中间件可配置
const fs = require('fs');
const path = require('path');

// 增强版耗时记录中间件
function createRequestLogger(logFilePath) {
  const logStream = fs.createWriteStream(path.join(__dirname, logFilePath), { flags: 'a' });
  
  return (req, res, next) => {
    const start = process.hrtime();
    const startDate = new Date().toISOString();
    
    res.on('finish', () => {
      const duration = process.hrtime(start);
      const durationMs = (duration[0] * 1e3 + duration[1] / 1e6).toFixed(3);
      
      const logEntry = `[${startDate}] ${req.ip} ${req.method} ${req.url} ${res.statusCode} ${durationMs}ms\n`;
      
      logStream.write(logEntry);
      console.log(logEntry.trim());
    });
    
    next();
  };
}

// 使用方式
app.use(createRequestLogger('requests.log'));

2. API密钥验证中间件的进阶版

问题:如何支持多种验证方式?

改进点

  1. 支持多种认证策略(API Key、JWT、Basic Auth)
  2. 异步验证支持
  3. 统一的错误处理
  4. 可扩展的工厂函数设计
// 支持多种验证策略的中间件工厂
function createAuthMiddleware(options = {}) {
  return async (req, res, next) => {
    try {
      // 策略1: API Key验证
      if (options.apiKey) {
        const apiKey = req.headers['x-api-key'] || req.query.apiKey;
        if (!apiKey) throw new Error('Missing API key');
        if (!options.apiKey.keys.includes(apiKey)) throw new Error('Invalid API key');
        req.authType = 'apiKey';
      }
      
      // 策略2: JWT验证
      if (options.jwt) {
        const token = req.headers.authorization?.split(' ')[1];
        if (!token) throw new Error('Missing token');
        
        const decoded = await verifyJWT(token, options.jwt.secret);
        req.user = decoded;
        req.authType = 'jwt';
      }
      
      // 策略3: 基本认证
      if (options.basicAuth) {
        const authHeader = req.headers.authorization;
        if (!authHeader || !authHeader.startsWith('Basic ')) {
          throw new Error('Missing basic auth');
        }
        
        const credentials = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
        const [username, password] = credentials.split(':');
        
        if (username !== options.basicAuth.user || password !== options.basicAuth.pass) {
          throw new Error('Invalid credentials');
        }
        
        req.authType = 'basic';
      }
      
      next();
    } catch (error) {
      res.status(401).json({ 
        error: 'Authentication failed',
        message: error.message
      });
    }
  };
}

// 使用示例
app.use(createAuthMiddleware({
  apiKey: {
    keys: ['123-abc', '456-def']
  },
  jwt: {
    secret: 'my-secret-key'
  }
}));

3. 组合中间件的模式进阶

问题:如何更灵活地组合中间件?

改进点

  1. 实现了类似Koa的中间件组合机制
  2. 添加条件中间件支持
  3. 更灵活的路径匹配
  4. 错误处理集成
// 中间件组合工具函数
function composeMiddlewares(...middlewares) {
  return (req, res, next) => {
    const dispatch = (i) => {
      if (i >= middlewares.length) return next();
      
      const middleware = middlewares[i];
      try {
        return middleware(req, res, () => dispatch(i + 1));
      } catch (err) {
        return next(err);
      }
    };
    
    return dispatch(0);
  };
}

// 条件中间件
function conditionalMiddleware(condition, middleware) {
  return (req, res, next) => {
    if (condition(req)) {
      return middleware(req, res, next);
    }
    next();
  };
}

// 使用示例
const isAdminRoute = req => req.path.startsWith('/admin');
const isApiRoute = req => req.path.startsWith('/api');

app.use(composeMiddlewares(
  requestLogger,
  conditionalMiddleware(
    isApiRoute,
    apiKeyValidator
  ),
  conditionalMiddleware(
    isAdminRoute,
    adminCheck
  )
));

// 等价于:
// app.use(requestLogger);
// app.use('/api', apiKeyValidator);
// app.use('/admin', adminCheck);

实用中间件模式

1. 数据转换中间件

function transformRequestBody(fields) {
  return (req, res, next) => {
    if (req.body) {
      for (const [field, transform] of Object.entries(fields)) {
        if (req.body[field] !== undefined) {
          req.body[field] = transform(req.body[field]);
        }
      }
    }
    next();
  };
}

// 使用示例
app.use(express.json());
app.use(transformRequestBody({
  email: v => v.toLowerCase().trim(),
  age: v => parseInt(v, 10),
  isActive: v => v === 'true'
}));

2. 响应包装中间件

function responseWrapper() {
  return (req, res, next) => {
    const originalSend = res.send;
    
    res.send = function(body) {
      if (res.statusCode >= 400) {
        originalSend.call(this, {
          success: false,
          error: body
        });
      } else {
        originalSend.call(this, {
          success: true,
          data: body
        });
      }
    };
    
    next();
  };
}

3. 请求限流中间件

function rateLimiter({ windowMs, maxRequests }) {
  const requests = new Map();
  
  setInterval(() => {
    requests.clear();
  }, windowMs);

  return (req, res, next) => {
    const ip = req.ip;
    const count = requests.get(ip) || 0;
    
    if (count >= maxRequests) {
      return res.status(429).send('Too many requests');
    }
    
    requests.set(ip, count + 1);
    next();
  };
}

最佳实践建议

  1. 单一职责:每个中间件只做一件事
  2. 可重用性:设计为可配置的工厂函数
  3. 错误处理:始终捕获同步和异步错误
  4. 性能考虑:避免在中间件中进行阻塞操作
  5. 文档注释:清晰说明中间件的用途和参数。

本节就到这里,下节将继续深入讨论示例。

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

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

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

相关文章

《前端面试题之 CSS篇(第一集)》

目录 1、CSS的盒模型2、CSS选择器及其优先级3、隐藏元素的方法有那些4、px、em、rem的区别及使用场景5、重排、重绘有什么区别6、水平垂直居中的实现7、CSS中可继承与不可继承属性有哪些8、Sass、Less 是什么?为什么要使用他们?9、CSS预处理器/后处理器是…

MySQL部分总结

mysql学习笔记,如有不足还请指出,谢谢。 外连接,内连接,全连接 外连接:左外、右外 内连接:自己和自己连接 全连接:左外连接右外链接 mysql unique字段 unique可以在数据库层面避免插入相同…

Linux Kernel 2

地址空间(Address Space) 一、物理地址空间(Physical Address Space) 物理地址空间 是指 RAM 和设备内存 在系统内存总线上所呈现的地址布局。 举例:在典型的 32 32 32 位 Intel 架构中, RAM&#xff08…

AndroidTV D贝桌面-v3.2.5-[支持文件传输]

AndroidTV D贝桌面 链接:https://pan.xunlei.com/s/VONXSBtgn8S_BsZxzjH_mHlAA1?pwdzet2# AndroidTV D贝桌面-v3.2.5[支持文件传输] 第一次使用的话,壁纸默认去掉的,不需要按遥控器上键,自己更换壁纸即可

线性方程组的解法

文章目录 线性方程组的解法认识一些基本的矩阵函数MATLAB 实现机电工程学院教学函数构造1.高斯消元法2.列主元消去法3. L U LU LU分解法 线性方程组的解法 看到以下线性方程组的一般形式:设有以下的 n n n阶线性方程组: A x b \mathbf{Ax}\mathbf{b} A…

Python赋能量子计算:算法创新与应用拓展

量子计算与Python结合的算法开发与应用研究 摘要 量子计算作为计算机科学的前沿技术,凭借其独特的计算能力在解决复杂问题方面展现出巨大潜力。Python作为一种高效、灵活的编程语言,为量子计算算法的开发提供了强大的支持。本文从研究学者的视角,系统探讨了量子计算与Pytho…

Java学习笔记(多线程):ReentrantLock 源码分析

本文是自己的学习笔记,主要参考资料如下 JavaSE文档 1、AQS 概述1.1、锁的原理1.2、任务队列1.2.1、结点的状态变化 1.3、加锁和解锁的简单流程 2、ReentrantLock2.1、加锁源码分析2.1.1、tryAcquire()的具体实现2.1.2、acquirQueued()的具体实现2.1.3、tryLock的具…

【软考系统架构设计师】系统配置与性能评价知识点

1、 常见的性能指标 主频外频*倍频 主频1/CPU时钟周期 CPI(Clock Per Instruction)平均每条指令的平均时间周期数 IPC(Instruction Per Clock)每时钟周期运行指令数 MIPS百万条指令每秒 MFLOPS百万个浮点操作每秒 字长影响运算的…

解锁Midjourney创作潜能:超详细提示词(Prompts)分类指南

AI生图自由!就来 ChatTools (https://chat.chattools.cn),畅享Midjourney免费无限绘画。同时体验GPT-4o、Claude 3.7 Sonnet、DeepSeek等强大模型。 为了帮助大家更好地驾驭Midjourney,我们精心整理并分类了大量常用且效果出众的提示词。无论…

大模型分布式推理和量化部署

一、小常识 1、计算大模型占用多少显存 对于一个7B(70亿)参数的模型,每个参数使用16位浮点数(等于 2个 Byte)表示,则模型的权重大小约为: 7010^9 parameters2 Bytes/parameter=14GB 70亿个参数每个参数占用2个字节=14GB 所以我们需要大于14GB的显存。注意14GB单纯是大…

【ROS】分布式通信架构

【ROS】分布式通信架构 前言环境要求主机设置(Master)从机设置(Slave)主机与从机通信测试本文示例启动ROS智能车激光雷达节点本地计算机配置与订阅 前言 在使用 ROS 时,我们常常会遇到某些设备计算能力不足的情况。例…

零基础HTML·笔记(持续更新…)

基础认知 HTML标签的结构 <strong>文字变粗</strong> &#xff1c;开始标签&#xff1e;内容&#xff1c;结束标签&#xff1e; 结构说明&#xff1a; 标签由<、>、1、英文单词或字母组成。并且把标签中<>包括起来的英文单词或字母称为标签名。常…

Visual Studio 2022 UI机器学习训练模块

VS你还是太超标了&#xff0c;现在机器学习都不用写代码了吗&#xff01;&#xff01; 右键项目解决方案&#xff0c;选择机器学习模型

FreeRTOS使任务处于阻塞态的API

在FreeRTOS中&#xff0c;任务进入阻塞状态通常是因为等待某个事件或资源。以下是常用的使任务进入阻塞态的API及其分类&#xff1a; 1. 任务延时 vTaskDelay(pdMS_TO_TICKS(ms)) 将任务阻塞固定时间&#xff08;相对延时&#xff0c;从调用时开始计算&#xff09;。 示例&…

独立开发者之网站的robots.txt文件如何生成和添加

robots.txt是一个存放在网站根目录下的文本文件&#xff0c;用于告诉搜索引擎爬虫哪些页面可以抓取&#xff0c;哪些页面不可以抓取。下面我将详细介绍如何生成和添加robots.txt文件。 什么是robots.txt文件&#xff1f; robots.txt是遵循"机器人排除协议"(Robots…

Leedcode刷题 | Day31_贪心算法05

一、学习任务 56. 合并区间代码随想录738. 单调递增的数字968. 监控二叉树 二、具体题目 1.56合并区间56. 合并区间 - 力扣&#xff08;LeetCode&#xff09; 给出一个区间的集合&#xff0c;请合并所有重叠的区间。 示例 1: 输入: intervals [[1,3],[2,6],[8,10],[15,1…

猫咪如厕检测与分类识别系统系列【一】 功能需求分析及猫咪分类特征提取

开发背景 家里养了三只猫咪&#xff0c;其中一只布偶猫经常出入厕所。但因为平时忙于学业&#xff0c;没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关&#xff0c;频繁如厕可能是泌尿问题&#xff0c;停留过久也可能是便秘或不适。为了更科学地了解牠的…

粘性定位(position:sticky)——微信小程序学习笔记

1. 简介 CSS 中的粘性定位&#xff08;Sticky positioning&#xff09;是一种特殊的定位方式&#xff0c;它可以使元素在滚动时保持在视窗的特定位置&#xff0c;类似于相对定位&#xff08;relative&#xff09;&#xff0c;但当页面滚动到元素的位置时&#xff0c;它会表现得…

最新版IDEA超详细图文安装教程(适用Mac系统)附安装包及补丁2025最新教程

目录 前言 一、IDEA最新版下载 二、IDEA安装 三、IDEA补丁 前言 IDEA&#xff08;IntelliJ IDEA&#xff09;是专为Java语言设计的集成开发环境&#xff08;IDE&#xff09;&#xff0c;由JetBrains公司开发&#xff0c;被公认为业界最优秀的Java开发工具之一。DEA全称Int…

JavaWeb-04-Web后端基础(SpringBootWeb、HTTP协议、分层解耦、IOC和DI)

目录 一、SpringBootWeb入门 1.1 概述 1.2 入门程序 1.2.1 需求 1.2.2 开发步骤 1.3 入门解析 二、HTTP协议 2.1 HTTP概述 2.1.1 介绍 2.1.2 特点 2.2 HTTP请求协议 2.2.1 介绍 2.2.2 获取请求数据 2.3 HTTP响应协议 2.3.1 格式介绍 2.3.2 响应状态码 2.3…