中间件是 Express 框架的核心设计模式之一。它提供了一种优雅的方式,在请求-响应周期中插入逻辑,实现诸如日志记录、身份验证、数据解析等功能。本章将深入探讨中间件的概念、类型、执行顺序及如何编写自定义中间件。
中间件是一个函数,它可以访问请求对象 (req)、响应对象 (res) 以及应用程序请求-响应循环中的下一个中间件函数 (next)。中间件可以执行以下任务:
如果当前中间件没有结束请求-响应循环,则必须调用 next() 将控制权传递给下一个中间件,否则请求会挂起。
Express 中间件根据应用范围和使用方式可以分为以下几类:
使用 app.use() 或 app.METHOD() 绑定的中间件,作用于整个应用或特定路由。
// 所有请求都会经过这个中间件
app.use((req, res, next) => {
console.log('请求时间:', Date.now());
next();
});
// 仅对 /user/:id 路径的请求生效
app.use('/user/:id', (req, res, next) => {
console.log('用户相关请求');
next();
});
绑定到 express.Router() 实例上的中间件,用于模块化路由。用法与应用级中间件相同,但作用范围限于该路由实例。
const router = express.Router();
// 此中间件仅在此路由中生效
router.use((req, res, next) => {
console.log('路由中间件');
next();
});
router.get('/', (req, res) => {
res.send('用户列表');
});
app.use('/users', router);
错误处理中间件有四个参数:(err, req, res, next)。它必须定义在所有其他中间件之后,用于捕获和处理错误。
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('出错了!');
});
Express 从 4.x 版本开始,除 express.static 外,其他内置中间件都被移除,需要单独安装。常用的内置中间件有:
express.json() - 解析 JSON 请求体。express.urlencoded({ extended: true }) - 解析 URL-encoded 请求体。express.static() - 托管静态文件。app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static('public'));
由社区提供的中间件,需通过 npm 安装后使用,例如 morgan(日志)、cors(跨域)、helmet(安全)等。
const morgan = require('morgan');
const cors = require('cors');
app.use(morgan('dev'));
app.use(cors());
中间件按照它们在代码中出现的顺序依次执行。如果某个中间件调用了 next(),控制权将传递给下一个匹配的中间件;如果调用 res.send() 等终结方法,则请求结束,后续中间件不会执行。
app.use((req, res, next) => {
console.log('中间件 1');
next(); // 传递给下一个
});
app.use((req, res, next) => {
console.log('中间件 2');
res.send('Hello'); // 请求结束
});
app.use((req, res, next) => {
// 不会执行
console.log('中间件 3');
});
顺序非常重要,例如静态文件中间件应该放在路由之前,而错误处理中间件必须放在最后。
自定义中间件通常是一个接受 req、res、next 参数的函数。下面是一个简单的日志中间件:
const logger = (req, res, next) => {
const { method, url } = req;
console.log(`${method} ${url}`);
next(); // 必须调用以继续
};
app.use(logger);
如果需要传递配置参数,可以编写一个返回中间件函数的函数:
const myLogger = (options) => {
return (req, res, next) => {
if (options.enable) {
console.log('日志已启用');
}
next();
};
};
app.use(myLogger({ enable: true }));
app.use((req, res, next) => {
req.requestTime = Date.now(); // 添加到请求对象
next();
});
app.get('/', (req, res) => {
res.send(`请求时间: ${req.requestTime}`);
});
const auth = (req, res, next) => {
const token = req.headers['authorization'];
if (token && token === 'secret-token') {
req.user = { name: 'John' };
next();
} else {
res.status(401).send('Unauthorized');
}
};
// 保护特定路由
app.get('/admin', auth, (req, res) => {
res.send(`欢迎 ${req.user.name}`);
});
使用内置中间件 express.static 托管静态资源:
app.use('/static', express.static(path.join(__dirname, 'public')));
next() 或发送响应,否则请求会挂起。next(err)。在异步中间件中,如果发生错误,必须将错误传递给 next,而不是抛出,否则错误不会被捕获。
app.use(async (req, res, next) => {
try {
const data = await someAsyncFunction();
req.data = data;
next();
} catch (err) {
next(err); // 传递给错误处理中间件
}
});
中间件是 Express 应用的骨架,理解其工作原理对于构建可维护、可扩展的应用至关重要。本章介绍了中间件的概念、类型、顺序和自定义方法。在下一章中,我们将学习如何使用模板引擎和静态文件服务来构建完整的 Web 应用。