您现在的位置是:网站首页 > 自定义中间件的开发方法文章详情

自定义中间件的开发方法

理解中间件的基本概念

Express中间件本质上是处理HTTP请求的函数,能够访问请求对象(req)、响应对象(res)和应用程序的请求-响应周期中的下一个中间件函数(next)。中间件可以执行以下操作:

  • 执行任何代码
  • 修改请求和响应对象
  • 结束请求-响应周期
  • 调用堆栈中的下一个中间件
function simpleMiddleware(req, res, next) {
  console.log('Request received at', new Date());
  next(); // 必须调用next()才能继续执行后续中间件
}

中间件的类型

Express中的中间件主要分为以下几种类型:

  1. 应用级中间件:使用app.use()或app.METHOD()绑定到应用程序实例
  2. 路由器级中间件:与应用级中间件类似,但绑定到express.Router()实例
  3. 错误处理中间件:专门处理错误的中间件,接收四个参数(err, req, res, next)
  4. 内置中间件:Express自带的中间件,如express.static
  5. 第三方中间件:社区开发的中间件,如body-parser

创建自定义中间件的基本结构

自定义中间件的基本结构包含三个参数:req、res和next。下面是一个最简单的自定义中间件示例:

const myMiddleware = (req, res, next) => {
  // 中间件逻辑
  console.log('Custom middleware executed');
  
  // 修改请求对象
  req.customProperty = 'Some value';
  
  // 继续处理下一个中间件
  next();
};

// 使用中间件
app.use(myMiddleware);

实现请求日志中间件

一个实用的自定义中间件例子是请求日志记录器,它可以记录每个请求的详细信息:

const requestLogger = (req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} [${duration}ms]`);
  });
  
  next();
};

app.use(requestLogger);

开发认证中间件

认证是Web应用中常见的需求,下面是一个简单的JWT认证中间件:

const jwt = require('jsonwebtoken');

const authMiddleware = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ error: 'Authentication required' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

// 保护路由
app.get('/protected', authMiddleware, (req, res) => {
  res.json({ message: 'Protected content', user: req.user });
});

数据验证中间件

数据验证是另一个常见场景,可以创建专门的验证中间件:

const validateUserInput = (req, res, next) => {
  const { username, email, password } = req.body;
  
  const errors = [];
  
  if (!username || username.length < 3) {
    errors.push('Username must be at least 3 characters');
  }
  
  if (!email || !email.includes('@')) {
    errors.push('Valid email is required');
  }
  
  if (!password || password.length < 6) {
    errors.push('Password must be at least 6 characters');
  }
  
  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }
  
  next();
};

app.post('/register', validateUserInput, (req, res) => {
  // 处理注册逻辑
});

错误处理中间件

错误处理中间件有四个参数,专门用于捕获和处理错误:

const errorHandler = (err, req, res, next) => {
  console.error(err.stack);
  
  const statusCode = err.statusCode || 500;
  const message = statusCode === 500 ? 'Something went wrong' : err.message;
  
  res.status(statusCode).json({
    error: {
      message,
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
    }
  });
};

// 使用方式 - 放在所有路由之后
app.use(errorHandler);

中间件组合与链式调用

多个中间件可以组合使用,形成处理链:

const logRequest = (req, res, next) => {
  console.log(`Incoming request: ${req.method} ${req.url}`);
  next();
};

const parseBody = (req, res, next) => {
  if (req.body) {
    req.parsedBody = JSON.stringify(req.body);
  }
  next();
};

const respond = (req, res) => {
  res.send(`Processed: ${req.parsedBody || 'No body'}`);
};

app.post('/process', logRequest, parseBody, respond);

异步中间件处理

现代JavaScript中,异步操作很常见,中间件也可以处理异步逻辑:

const asyncMiddleware = fn => (req, res, next) => {
  Promise.resolve(fn(req, res, next))
    .catch(next);
};

// 使用示例
const fetchUserData = asyncMiddleware(async (req, res, next) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    throw new Error('User not found');
  }
  req.user = user;
  next();
});

app.get('/users/:id', fetchUserData, (req, res) => {
  res.json(req.user);
});

中间件的配置选项

更灵活的自定义中间件可以接受配置选项:

const createRateLimiter = (options = {}) => {
  const {
    windowMs = 15 * 60 * 1000, // 15分钟
    max = 100, // 每个IP最多100个请求
    message = 'Too many requests'
  } = options;
  
  const requests = new Map();
  
  return (req, res, next) => {
    const ip = req.ip;
    const now = Date.now();
    
    if (!requests.has(ip)) {
      requests.set(ip, { count: 1, startTime: now });
      return next();
    }
    
    const record = requests.get(ip);
    
    if (now - record.startTime > windowMs) {
      requests.set(ip, { count: 1, startTime: now });
      return next();
    }
    
    if (record.count >= max) {
      return res.status(429).json({ error: message });
    }
    
    record.count++;
    next();
  };
};

// 使用带配置的中间件
app.use(createRateLimiter({ max: 50 }));

中间件的测试方法

测试自定义中间件是确保其可靠性的重要环节:

const testMiddleware = require('supertest');
const express = require('express');

describe('Auth Middleware', () => {
  let app;
  
  beforeEach(() => {
    app = express();
    app.use(express.json());
    app.use(authMiddleware);
    app.get('/test', (req, res) => res.json({ user: req.user }));
  });
  
  it('should reject requests without token', async () => {
    const res = await testMiddleware(app).get('/test');
    expect(res.statusCode).toBe(401);
  });
  
  it('should accept requests with valid token', async () => {
    const token = jwt.sign({ id: 1 }, process.env.JWT_SECRET);
    const res = await testMiddleware(app)
      .get('/test')
      .set('Authorization', `Bearer ${token}`);
    expect(res.statusCode).toBe(200);
  });
});

中间件的性能考虑

开发中间件时需要考虑性能影响:

  1. 避免阻塞操作:长时间运行的同步操作会阻塞事件循环
  2. 缓存常用数据:如认证信息、配置数据等
  3. 精简中间件栈:只使用必要的中间件
  4. 异步处理:将I/O操作异步化
// 优化后的缓存中间件示例
const createCacheMiddleware = (ttl = 60) => {
  const cache = new Map();
  
  return (req, res, next) => {
    const key = req.originalUrl;
    const cached = cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < ttl * 1000) {
      return res.json(cached.data);
    }
    
    const originalSend = res.json;
    res.json = (data) => {
      cache.set(key, { data, timestamp: Date.now() });
      originalSend.call(res, data);
    };
    
    next();
  };
};

中间件的实际应用场景

自定义中间件在实际项目中有广泛的应用:

  1. 请求转换:修改请求数据格式
  2. 响应处理:统一响应格式
  3. 性能监控:记录请求处理时间
  4. 流量控制:实现限流、熔断
  5. AOP编程:实现日志、事务等横切关注点
// 统一响应格式的中间件
const responseFormatter = (req, res, next) => {
  const originalJson = res.json;
  
  res.json = (data) => {
    originalJson.call(res, {
      success: true,
      data,
      timestamp: new Date().toISOString()
    });
  };
  
  next();
};

app.use(responseFormatter);

中间件的调试技巧

调试自定义中间件时可以采用以下方法:

  1. 日志记录:详细记录中间件执行流程
  2. 断点调试:使用Node.js调试工具
  3. 单元测试:隔离测试中间件功能
  4. 错误边界:捕获并处理中间件中的异常
// 带有调试功能的中间件
const debugMiddleware = (namespace) => {
  return (req, res, next) => {
    const start = Date.now();
    console.log(`[${namespace}] Start processing`, req.method, req.url);
    
    res.on('finish', () => {
      console.log(
        `[${namespace}] Completed in ${Date.now() - start}ms`,
        res.statusCode
      );
    });
    
    next();
  };
};

app.use(debugMiddleware('app'));

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步