讲讲Node.js中间件实现

5 阅读6分钟

在Node.js Web开发中,中间件是实现请求分层处理、模块化开发的核心技术。本文结合对话中的核心疑问与实践案例,系统梳理中间件实现的完整逻辑,帮助读者彻底吃透这一关键知识点。

一、中间件的本质:模块化的请求处理单元

中间件并非Node.js内置特性,而是一套标准化的HTTP请求处理函数规范,其核心特点可概括为三点:

  1. 职责单一:每个中间件仅专注于一项任务,如日志记录、权限校验、异步数据查询、响应处理等,实现“单一职责原则”,便于维护与复用。
  2. 统一接口:普通中间件固定接收(req, res, next)三个参数,其中reqres是Node.js原生请求/响应对象,next是控制权传递回调函数,用于触发下一个中间件。
  3. 链式执行:多个中间件按注册顺序形成处理链,请求在链中流转,完成分层处理,这一特性依赖于“洋葱模型”的执行逻辑。

二、核心原理:洋葱模型的执行逻辑

洋葱模型是中间件执行的核心,也是理解中间件流转的关键,其本质是请求处理分为“进入”和“退出”两个阶段,形成“从外到内进入,从内到外退出”的闭环:

  1. 进入阶段:请求从第一个注册的中间件开始,按顺序执行逻辑,当中间件调用next()时,控制权传递给下一个中间件,直至到达核心业务处理中间件(无next()调用)。
  2. 退出阶段:核心业务处理完成后,请求会按注册的反向顺序,执行每个中间件中next()之后的逻辑,完成收尾操作。

通过一段极简代码可直观理解执行顺序:

// 注册顺序:中间件1 → 中间件2 → 核心业务
use((req, res, next) => {
  console.log('1. 中间件1 进入');
  next();
  console.log('4. 中间件1 退出'); // 退出阶段执行
});
use((req, res, next) => {
  console.log('2. 中间件2 进入');
  next();
  console.log('3. 中间件2 退出'); // 退出阶段执行
});
use((req, res) => {
  console.log('核心:业务处理'); // 终止中间件链
  res.end('ok');
});
// 执行结果:1.进入 → 2.进入 → 核心处理 → 3.退出 → 4.退出

三、简易中间件框架的手动实现

8.4节的核心的是通过手写极简框架,拆解中间件的调度原理,完整实现需分四步,同时支持异步处理与错误捕获:

1. 初始化中间件存储容器

用数组存储所有注册的中间件,保证注册顺序与执行顺序一致,该数组是连接中间件注册与调度的核心桥梁。

const middlewareList = []; // 存储普通中间件与错误处理中间件

2. 实现use方法:注册中间件

use方法的唯一职责是校验中间件合法性(必须为函数),并将其存入数组,仅负责“存储”不执行任何逻辑,为后续调度做准备。

function use(middlewareFunc) {
  if (typeof middlewareFunc !== 'function') {
    throw new TypeError('中间件必须是函数类型');
  }
  middlewareList.push(middlewareFunc);
}

3. 实现核心调度器:handleRequest

调度器是中间件执行的“中枢”,负责定义next函数、遍历中间件数组、处理控制权传递与错误捕获,核心逻辑如下:

  • currentIndex索引记录当前执行的中间件位置,实现顺序遍历;
  • next函数支持无参(传递控制权)和有参(传递错误)两种模式;
  • 区分普通中间件(3个参数)与错误处理中间件(4个参数),异常时跳过普通中间件,直接匹配错误处理中间件;
  • try/catch捕获同步异常,异步异常需在回调中通过next(err)主动传递。
function handleRequest(req, res) {
  let currentIndex = 0;
  function next(err) {
    // 边界:所有中间件执行完毕
    if (currentIndex >= middlewareList.length) {
      const msg = err ? `错误:${err.message}` : '无中间件处理请求';
      return res.end(msg);
    }
    const currentMiddleware = middlewareList[currentIndex++];
    try {
      const isErrorMiddleware = currentMiddleware.length === 4;
      if (err) {
        // 有错误:仅执行错误处理中间件
        isErrorMiddleware ? currentMiddleware(err, req, res, next) : next(err);
      } else {
        // 无错误:仅执行普通中间件
        !isErrorMiddleware ? currentMiddleware(req, res, next) : next();
      }
    } catch (syncErr) {
      next(syncErr); // 捕获同步异常
    }
  }
  next(); // 启动中间件链
}

4. 绑定HTTP服务器:触发中间件链

通过Node.js原生http.createServer创建服务器,将调度器handleRequest作为请求处理回调,搭建HTTP请求与中间件框架的桥梁。

四、关键疑问解析:usenext的关联、createServer的作用

1. usenext的间接关联

二者无直接绑定关系,完全通过middlewareList数组实现“供需对接”:

  • use是“供应商”:仅负责将中间件存入数组,不接触next
  • next是“消费者”:从数组中按顺序取出中间件执行,并将自身作为参数传递给中间件,中间件调用next()即可触发下一个中间件,形成闭环。

2. createServer的核心作用

作为整个框架的“入口大门”,createServer承担两大职责:

  • 底层工作:创建HTTP服务器实例,处理TCP连接、HTTP请求解析、响应封装等底层细节,无需手动关注;
  • 入口绑定:接收handleRequest作为回调,每次有客户端请求时,自动触发调度器,传入req/res对象,启动中间件链执行。

五、与Express源码的对比

手写极简框架复刻了Express中间件的核心逻辑,二者差异仅在于Express的封装更完善,核心一致性体现在:

  1. 中间件存储:均用数组保存中间件,app.use与自定义use逻辑一致,仅Express通过Layer层封装中间件与路由信息;
  2. 调度逻辑:Express的app.handle方法与自定义handleRequest原理相同,均通过索引遍历、next递归调度、异常统一传递实现;
  3. 错误处理:均要求错误处理中间件接收4个参数,触发规则、异常捕获逻辑完全一致。

六、完整执行流程串联

从初始化到请求处理,全链路可分为七个阶段:

  1. 初始化:创建中间件数组,定义usehandleRequest函数;
  2. 注册:调用use多次,将日志、异步、业务、错误处理中间件存入数组;
  3. 建服:createServer(handleRequest)创建服务器,绑定请求回调;
  4. 监听:server.listen(3000)启动服务,等待客户端请求;
  5. 触发:客户端请求到达,http模块触发handleRequest,传入req/res
  6. 执行:handleRequest启动next(),遍历中间件数组,完成请求处理或错误捕获;
  7. 收尾:响应发送给客户端,中间件链终止。

七、核心总结

Node.js中间件实现的核心是“数组存储+洋葱模型+next控制权传递”:use负责存储中间件,createServer提供请求入口,handleRequestnext实现调度与流转,错误处理中间件则保障异常可控。理解这一逻辑,就能看透Express等主流框架的底层原理,为自定义中间件、排查请求流转问题奠定基础。