中间件机制
中间件概述
中间件(Middleware)是Node.js Web应用中的一个核心概念,特别是在Express.js等Web框架中。中间件本质上是一个函数,它可以访问请求对象(req)、响应对象(res)以及应用程序的下一个中间件函数(next)。
中间件函数可以执行以下任务:
- 执行任何代码
- 修改请求和响应对象
- 终结请求-响应循环
- 调用堆栈中的下一个中间件函数
Express.js中间件
Express.js是Node.js最流行的Web框架之一,它的中间件机制非常强大和灵活。下面我们主要介绍Express.js的中间件机制。
Express.js中间件的基本结构
function middlewareFunction(req, res, next) {
// 执行代码
// 调用next()将控制权传递给下一个中间件
next();
// 如果需要处理错误,可以调用next(err)
// next(new Error('Something went wrong'));
}
使用中间件
在Express.js中,我们可以使用app.use()方法来使用中间件:
const express = require('express');
const app = express();
// 一个简单的中间件
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);
中间件的类型
1. 应用级中间件
应用级中间件绑定到app对象,使用app.use()和app.METHOD()方法。
const express = require('express');
const app = express();
// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
// 挂载在/api路径下的中间件,任何指向/api的请求都会执行它
app.use('/api', (req, res, next) => {
console.log('API request received');
next();
});
// 路由级中间件,只处理GET请求
app.get('/user/:id', (req, res, next) => {
res.send(`User ID: ${req.params.id}`);
});
2. 路由级中间件
路由级中间件与应用级中间件类似,但它绑定到express.Router()实例。
const express = require('express');
const app = express();
const router = express.Router();
// 路由级中间件
router.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
router.get('/', (req, res) => {
res.send('Router home page');
});
router.get('/about', (req, res) => {
res.send('About page');
});
// 将路由挂载到应用
app.use('/router', router);
3. 错误处理中间件
错误处理中间件有四个参数:err, req, res, next。当调用next(err)时,Express会跳过所有非错误处理中间件,直接执行错误处理中间件。
const express = require('express');
const app = express();
app.get('/error', (req, res, next) => {
next(new Error('Something went wrong'));
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
4. 内置中间件
Express 4.x提供了一些内置的中间件:
express.static:用于提供静态资源express.json:用于解析JSON请求体express.urlencoded:用于解析URL编码的请求体
const express = require('express');
const app = express();
// 提供静态资源
app.use(express.static('public'));
// 解析JSON请求体
app.use(express.json());
// 解析URL编码的请求体
app.use(express.urlencoded({ extended: true }));
5. 第三方中间件
我们可以使用许多第三方中间件来扩展Express的功能。使用第三方中间件前,需要先安装它:
npm install morgan cors helmet
然后在应用中使用:
const express = require('express');
const app = express();
const morgan = require('morgan'); // 日志中间件
const cors = require('cors'); // 跨域资源共享中间件
const helmet = require('helmet'); // 安全增强中间件
// 使用第三方中间件
app.use(morgan('dev')); // 开发环境日志
app.use(cors()); // 允许跨域请求
app.use(helmet()); // 增强应用安全性
中间件的执行顺序
在Express中,中间件的执行顺序非常重要,它按照app.use()和路由定义的顺序执行。
const express = require('express');
const app = express();
// 中间件1 - 首先执行
app.use((req, res, next) => {
console.log('Middleware 1 executed');
next();
});
// 中间件2 - 第二个执行
app.use((req, res, next) => {
console.log('Middleware 2 executed');
next();
});
// 路由处理函数 - 最后执行
app.get('/', (req, res) => {
console.log('Route handler executed');
res.send('Hello World!');
});
中间件链
我们可以在一个路由处理程序中使用多个中间件,形成中间件链。
const express = require('express');
const app = express();
// 定义多个中间件函数
const middleware1 = (req, res, next) => {
console.log('Middleware 1');
req.customData = 'Data from middleware 1';
next();
};
const middleware2 = (req, res, next) => {
console.log('Middleware 2');
console.log('Received data:', req.customData);
next();
};
// 在路由中使用中间件链
app.get('/chain', [middleware1, middleware2], (req, res) => {
res.send('Middleware chain executed successfully');
});
中间件的实际应用
1. 身份验证中间件
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ message: '未提供身份验证令牌' });
}
try {
// 验证令牌...
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ message: '无效的令牌' });
}
}
// 保护需要身份验证的路由
app.get('/api/protected', authenticate, (req, res) => {
res.json({ message: '这是受保护的资源', user: req.user });
});
2. 日志中间件
function logger(req, res, next) {
const { method, url, headers } = req;
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${method} ${url}`);
console.log('Headers:', headers);
// 记录响应状态码
const originalSend = res.send;
res.send = function(body) {
console.log(`Response status: ${res.statusCode}`);
return originalSend.call(this, body);
};
next();
}
app.use(logger);
3. 缓存中间件
const cache = new Map();
function cacheMiddleware(req, res, next) {
const cacheKey = req.originalUrl;
if (cache.has(cacheKey)) {
console.log('Cache hit for:', cacheKey);
return res.json(cache.get(cacheKey));
}
console.log('Cache miss for:', cacheKey);
// 重写res.json方法以缓存响应
const originalJson = res.json;
res.json = function(body) {
cache.set(cacheKey, body);
return originalJson.call(this, body);
};
next();
}
// 只对GET请求使用缓存中间件
app.get('/api/cacheable/*', cacheMiddleware);
4. 限流中间件
const rateLimit = require('express-rate-limit');
// 创建限流中间件
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个IP最多100个请求
message: {
error: '请求过于频繁,请稍后再试'
}
});
// 应用限流中间件
app.use('/api', limiter);
自定义中间件的最佳实践
- 保持中间件的单一职责:每个中间件只做一件事
- 命名清晰:使用描述性的名称来命名中间件函数
- 错误处理:正确处理中间件中的错误,并调用
next(err)传递给错误处理中间件 - 避免阻塞:中间件应该快速执行,避免包含阻塞操作
- 记录日志:对于重要的中间件,可以添加日志记录
- 模块化:将中间件逻辑封装在单独的模块中,便于重用
中间件执行流程图
[客户端请求] → [应用级中间件] → [路由级中间件] → [路由处理函数] → [客户端响应]
↓
[错误处理中间件]
Express与Koa的中间件比较
Express和Koa都是流行的Node.js Web框架,但它们的中间件机制有一些区别:
Express中间件
- 使用回调函数形式
- 按照注册顺序执行
- 错误处理中间件有四个参数
app.use((req, res, next) => {
// 中间件逻辑
next(); // 调用下一个中间件
});
Koa中间件
- 使用异步函数和Promise
- 采用洋葱模型执行
- 上下文对象(ctx)包含请求和响应
app.use(async (ctx, next) => {
// 洋葱模型外层
await next(); // 等待下一个中间件执行完成
// 洋葱模型内层(反向执行)
});
总结
中间件机制是Node.js Web开发中的核心概念,它提供了一种灵活的方式来处理HTTP请求和响应。通过合理使用中间件,我们可以:
- 分离关注点,使代码更加模块化
- 重用代码,提高开发效率
- 增强应用功能,如身份验证、日志记录、缓存等
- 更好地组织和维护应用程序逻辑
掌握中间件的使用,对于构建高效、可维护的Node.js Web应用至关重要。