Express 中间件机制

核心知识点

中间件概念

中间件是 Express 的核心概念,它是一个函数,能够访问请求对象 (req)、响应对象 (res) 和应用的请求-响应循环中的下一个中间件函数 (next)。

中间件的主要功能:

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

中间件类型

Express 中有几种类型的中间件:

  1. 应用级中间件:使用 app.use()app.METHOD() 绑定到应用实例
  2. 路由级中间件:使用 router.use()router.METHOD() 绑定到路由实例
  3. 错误处理中间件:带有四个参数 (err, req, res, next) 的中间件
  4. 内置中间件:Express 内置的中间件,如 express.static()express.json()
  5. 第三方中间件:由社区开发的中间件,如 morganhelmet

中间件执行顺序

中间件按照它们在代码中定义的顺序执行。对于路由中间件,它们按照路由匹配的顺序执行。

错误处理中间件

错误处理中间件是一种特殊的中间件,它有四个参数 (err, req, res, next),用于捕获和处理应用中的错误。

中间件链

多个中间件可以组成一个链,每个中间件可以选择是否将请求传递给下一个中间件。

实用案例

案例一:应用级中间件

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

// 应用级中间件 - 日志
app.use((req, res, next) => {
  console.log(`${new Date()} - ${req.method} ${req.url}`);
  next(); // 调用下一个中间件
});

// 应用级中间件 - 身份验证
app.use((req, res, next) => {
  // 模拟身份验证
  const authHeader = req.headers.authorization;
  if (authHeader) {
    req.user = { id: 1, name: 'Authenticated User' };
  } else {
    req.user = { id: 0, name: 'Guest' };
  }
  next();
});

// 应用级中间件 - 计时
app.use((req, res, next) => {
  const start = Date.now();
  
  // 监听响应结束事件
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`请求处理时间: ${duration}ms`);
  });
  
  next();
});

// 路由
app.get('/', (req, res) => {
  res.send(`Hello, ${req.user.name}!`);
});

app.get('/protected', (req, res) => {
  if (req.user.id > 0) {
    res.send('这是受保护的页面');
  } else {
    res.status(401).send('未授权');
  }
});

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

案例二:路由级中间件

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

// 创建路由实例
const userRouter = express.Router();
const adminRouter = express.Router();

// 用户路由的中间件
userRouter.use((req, res, next) => {
  console.log('用户路由中间件');
  next();
});

// 管理员路由的中间件
adminRouter.use((req, res, next) => {
  console.log('管理员路由中间件');
  // 模拟管理员权限检查
  const isAdmin = req.headers['x-admin'] === 'true';
  if (!isAdmin) {
    return res.status(403).send('没有管理员权限');
  }
  next();
});

// 用户路由
userRouter.get('/', (req, res) => {
  res.send('用户首页');
});

userRouter.get('/profile', (req, res) => {
  res.send('用户资料');
});

// 管理员路由
adminRouter.get('/', (req, res) => {
  res.send('管理员首页');
});

adminRouter.get('/users', (req, res) => {
  res.send('管理用户');
});

// 注册路由
app.use('/user', userRouter);
app.use('/admin', adminRouter);

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

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

案例三:错误处理中间件

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

// 应用级中间件
app.use(express.json());

// 路由 - 正常路由
app.get('/', (req, res) => {
  res.send('Hello, Express!');
});

// 路由 - 会抛出错误的路由
app.get('/error', (req, res) => {
  throw new Error('故意抛出的错误');
});

// 路由 - 异步错误
app.get('/async-error', async (req, res, next) => {
  try {
    // 模拟异步错误
    await new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error('异步操作失败')), 1000);
    });
  } catch (error) {
    next(error); // 传递错误给错误处理中间件
  }
});

// 404 错误处理
app.use((req, res, next) => {
  const error = new Error('Not Found');
  error.status = 404;
  next(error); // 传递给错误处理中间件
});

// 全局错误处理中间件
app.use((err, req, res, next) => {
  // 设置状态码
  const statusCode = err.status || 500;
  
  // 记录错误
  console.error('错误:', err);
  
  // 发送错误响应
  res.status(statusCode).json({
    error: {
      status: statusCode,
      message: err.message || 'Internal Server Error'
    }
  });
});

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

案例四:内置中间件

const express = require('express');
const path = require('path');
const app = express();
const port = 3000;

// 内置中间件 - 解析 JSON 请求体
app.use(express.json());

// 内置中间件 - 解析 URL 编码的表单数据
app.use(express.urlencoded({ extended: true }));

// 内置中间件 - 提供静态文件服务
app.use(express.static(path.join(__dirname, 'public')));

// 路由
app.get('/', (req, res) => {
  res.send('Express 内置中间件示例');
});

app.post('/api/data', (req, res) => {
  res.json({
    message: '数据接收成功',
    data: req.body
  });
});

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

案例五:第三方中间件

const express = require('express');
const morgan = require('morgan'); // 日志中间件
const helmet = require('helmet'); // 安全中间件
const cors = require('cors'); // 跨域中间件
const app = express();
const port = 3000;

// 第三方中间件
app.use(morgan('combined')); // 详细日志
app.use(helmet()); // 安全头部
app.use(cors()); // 允许跨域

// 内置中间件
app.use(express.json());

// 路由
app.get('/', (req, res) => {
  res.send('第三方中间件示例');
});

app.get('/api/data', (req, res) => {
  res.json({
    message: 'API 数据',
    data: [1, 2, 3, 4, 5]
  });
});

// 错误处理
app.use((err, req, res, next) => {
  console.error('错误:', err);
  res.status(500).json({ error: '服务器内部错误' });
});

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

案例六:中间件链

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

// 中间件 1:日志
const loggerMiddleware = (req, res, next) => {
  console.log('中间件 1: 记录请求');
  next();
};

// 中间件 2:验证
const authMiddleware = (req, res, next) => {
  console.log('中间件 2: 验证用户');
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) {
    return res.status(401).send('缺少 API Key');
  }
  if (apiKey !== 'secret-key') {
    return res.status(401).send('无效的 API Key');
  }
  req.user = { id: 1, name: '验证用户' };
  next();
};

// 中间件 3:数据验证
const dataValidationMiddleware = (req, res, next) => {
  console.log('中间件 3: 验证数据');
  if (req.method === 'POST' || req.method === 'PUT') {
    if (!req.body) {
      return res.status(400).send('缺少请求体');
    }
    if (!req.body.name) {
      return res.status(400).send('缺少 name 字段');
    }
  }
  next();
};

// 应用中间件链
app.use(loggerMiddleware);

// 特定路由的中间件链
app.post('/api/users', authMiddleware, dataValidationMiddleware, (req, res) => {
  console.log('处理 POST /api/users 请求');
  res.json({
    message: '用户创建成功',
    user: req.body,
    authenticatedUser: req.user
  });
});

// 另一个路由
app.get('/api/public', (req, res) => {
  console.log('处理 GET /api/public 请求');
  res.json({
    message: '公共 API',
    data: '无需验证即可访问'
  });
});

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

学习目标

  1. 理解中间件概念:掌握中间件的基本原理和工作机制
  2. 使用不同类型的中间件:学会使用应用级、路由级、错误处理等中间件
  3. 控制中间件执行顺序:理解中间件的执行顺序并合理安排
  4. 实现错误处理:学会使用错误处理中间件捕获和处理错误
  5. 使用内置中间件:掌握 Express 内置中间件的使用
  6. 集成第三方中间件:学会安装和使用第三方中间件
  7. 构建中间件链:能够创建和管理复杂的中间件链

代码优化建议

1. 中间件模块化

不好的做法

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

// 所有中间件都在一个文件中
app.use((req, res, next) => {
  // 日志中间件
  console.log(`${new Date()} ${req.method} ${req.url}`);
  next();
});

app.use((req, res, next) => {
  // 认证中间件
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).send('Unauthorized');
  }
  next();
});

// 更多中间件...

好的做法

// middlewares/logger.js
const loggerMiddleware = (req, res, next) => {
  console.log(`${new Date()} ${req.method} ${req.url}`);
  next();
};
module.exports = loggerMiddleware;

// middlewares/auth.js
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).send('Unauthorized');
  }
  next();
};
module.exports = authMiddleware;

// app.js
const express = require('express');
const app = express();
const loggerMiddleware = require('./middlewares/logger');
const authMiddleware = require('./middlewares/auth');

app.use(loggerMiddleware);
app.use(authMiddleware);

2. 条件中间件

不好的做法

app.use((req, res, next) => {
  if (req.url.startsWith('/api')) {
    // 只对 API 路由应用的逻辑
    const apiKey = req.headers['x-api-key'];
    if (!apiKey) {
      return res.status(401).send('API Key required');
    }
  }
  next();
});

好的做法

// 为特定路径应用中间件
app.use('/api', (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) {
    return res.status(401).send('API Key required');
  }
  next();
});

3. 异步中间件错误处理

不好的做法

app.use(async (req, res, next) => {
  // 没有错误处理的异步操作
  const user = await getUserFromDatabase(req.headers.authorization);
  req.user = user;
  next();
});

好的做法

app.use(async (req, res, next) => {
  try {
    const user = await getUserFromDatabase(req.headers.authorization);
    req.user = user;
    next();
  } catch (error) {
    next(error); // 传递错误给错误处理中间件
  }
});

4. 中间件配置选项

不好的做法

app.use((req, res, next) => {
  // 硬编码的配置
  const maxAge = 3600000;
  res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
  next();
});

好的做法

// 可配置的中间件
function cacheMiddleware(options = {}) {
  const {
    maxAge = 3600000,
    public: isPublic = true
  } = options;
  
  return (req, res, next) => {
    const cacheControl = `${isPublic ? 'public' : 'private'}, max-age=${maxAge / 1000}`;
    res.setHeader('Cache-Control', cacheControl);
    next();
  };
}

// 使用中间件
app.use(cacheMiddleware({ maxAge: 7200000 })); // 2小时

常见问题与解决方案

问题1:中间件不执行

原因

  • 中间件顺序错误
  • 没有调用 next()
  • 前面的中间件已经终结了请求-响应循环

解决方案

  • 调整中间件的顺序,确保它在路由之前
  • 确保在中间件中调用 next()
  • 检查前面的中间件是否正确传递了请求

问题2:错误处理中间件不捕获错误

原因

  • 错误处理中间件定义在其他中间件之前
  • 异步错误没有使用 next(error) 传递
  • 错误处理中间件参数不正确

解决方案

  • 确保错误处理中间件定义在所有其他中间件之后
  • 在异步代码中使用 try/catch 捕获错误并传递给 next()
  • 确保错误处理中间件有四个参数 (err, req, res, next)

问题3:中间件执行顺序混乱

原因

  • 中间件定义顺序不合理
  • 路由中间件和应用中间件混合使用

解决方案

  • 按照逻辑顺序定义中间件:日志 → 认证 → 数据解析 → 业务逻辑 → 错误处理
  • 合理组织路由中间件,确保它们在正确的位置

问题4:中间件导致性能问题

原因

  • 中间件过多
  • 中间件执行耗时操作
  • 中间件链过长

解决方案

  • 只使用必要的中间件
  • 避免在中间件中执行耗时操作,如数据库查询
  • 合理组织中间件链,避免不必要的中间件

问题5:第三方中间件冲突

原因

  • 多个第三方中间件功能重叠
  • 中间件版本不兼容

解决方案

  • 仔细阅读中间件文档,了解它们的功能
  • 只使用必要的中间件
  • 确保中间件版本兼容

总结

通过本教程的学习,你应该能够:

  1. 理解 Express 中间件的基本概念和工作原理
  2. 掌握不同类型的中间件,包括应用级、路由级、错误处理中间件等
  3. 理解中间件的执行顺序和中间件链的工作机制
  4. 学会使用 Express 内置中间件和第三方中间件
  5. 实现自定义中间件来满足特定需求
  6. 合理组织中间件,提高应用的可维护性和性能
  7. 使用错误处理中间件捕获和处理应用中的错误

中间件是 Express 框架的核心特性,它提供了一种灵活的方式来处理 HTTP 请求和响应。掌握中间件的使用,对于构建功能完整、性能优异的 Express 应用至关重要。在实际开发中,你应该根据应用的需求,选择和实现合适的中间件,以提高开发效率和应用质量。

« 上一篇 Express 路由与请求处理 下一篇 » Node.js 数据库集成