Express 中间件机制
核心知识点
中间件概念
中间件是 Express 的核心概念,它是一个函数,能够访问请求对象 (req)、响应对象 (res) 和应用的请求-响应循环中的下一个中间件函数 (next)。
中间件的主要功能:
- 执行任何代码
- 修改请求和响应对象
- 终结请求-响应循环
- 调用堆栈中的下一个中间件
中间件类型
Express 中有几种类型的中间件:
- 应用级中间件:使用
app.use()和app.METHOD()绑定到应用实例 - 路由级中间件:使用
router.use()和router.METHOD()绑定到路由实例 - 错误处理中间件:带有四个参数
(err, req, res, next)的中间件 - 内置中间件:Express 内置的中间件,如
express.static()、express.json()等 - 第三方中间件:由社区开发的中间件,如
morgan、helmet等
中间件执行顺序
中间件按照它们在代码中定义的顺序执行。对于路由中间件,它们按照路由匹配的顺序执行。
错误处理中间件
错误处理中间件是一种特殊的中间件,它有四个参数 (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}`);
});学习目标
- 理解中间件概念:掌握中间件的基本原理和工作机制
- 使用不同类型的中间件:学会使用应用级、路由级、错误处理等中间件
- 控制中间件执行顺序:理解中间件的执行顺序并合理安排
- 实现错误处理:学会使用错误处理中间件捕获和处理错误
- 使用内置中间件:掌握 Express 内置中间件的使用
- 集成第三方中间件:学会安装和使用第三方中间件
- 构建中间件链:能够创建和管理复杂的中间件链
代码优化建议
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:第三方中间件冲突
原因:
- 多个第三方中间件功能重叠
- 中间件版本不兼容
解决方案:
- 仔细阅读中间件文档,了解它们的功能
- 只使用必要的中间件
- 确保中间件版本兼容
总结
通过本教程的学习,你应该能够:
- 理解 Express 中间件的基本概念和工作原理
- 掌握不同类型的中间件,包括应用级、路由级、错误处理中间件等
- 理解中间件的执行顺序和中间件链的工作机制
- 学会使用 Express 内置中间件和第三方中间件
- 实现自定义中间件来满足特定需求
- 合理组织中间件,提高应用的可维护性和性能
- 使用错误处理中间件捕获和处理应用中的错误
中间件是 Express 框架的核心特性,它提供了一种灵活的方式来处理 HTTP 请求和响应。掌握中间件的使用,对于构建功能完整、性能优异的 Express 应用至关重要。在实际开发中,你应该根据应用的需求,选择和实现合适的中间件,以提高开发效率和应用质量。