题目
设计一个Express中间件记录HTTP请求响应时间并返回在响应头中
信息
- 类型:问答
- 难度:⭐⭐
考点
中间件机制, HTTP模块, 性能监控, 响应头处理
快速回答
实现步骤:
- 创建中间件函数捕获请求开始时间
- 监听响应'finish'事件计算持续时间
- 将结果写入响应头(如X-Response-Time)
- 调用next()传递控制权
关键代码:
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
res.setHeader('X-Response-Time', `${duration}ms`);
});
next();
});
## 解析
原理说明
在Node.js的HTTP模块中,当服务器完成响应时会触发'finish'事件。中间件通过以下流程工作:
- 请求进入时记录起始时间戳
- 挂载'finish'事件监听器到响应对象
- 响应结束时计算时间差并写入响应头
- 通过next()保证后续中间件执行
代码实现与示例
// 基础实现
app.use((req, res, next) => {
const start = process.hrtime.bigint(); // 使用高精度时间
res.on('finish', () => {
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1e6; // 纳秒转毫秒
res.setHeader('X-Response-Time', `${duration.toFixed(2)}ms`);
});
next();
});
// 测试路由
app.get('/', (req, res) => {
setTimeout(() => res.send('Hello'), 100); // 模拟延迟
});最佳实践
- 高精度计时:使用
process.hrtime替代Date.now获取纳秒级精度 - 错误处理:添加'close'事件监听处理连接中断场景
res.on('close', () => { // 清理逻辑 }); - 性能优化:避免在中间件中执行阻塞操作
- 标准化头字段:推荐使用
X-Response-Time而非自定义名称
常见错误
| 错误类型 | 后果 | 解决方案 |
|---|---|---|
| 忘记调用next() | 请求挂起 | 确保所有路径都调用next() |
| 多次设置响应头 | 报'Can't set headers after sent'错误 | 仅在finish/close事件中操作 |
| 时间计算不准确 | 负值或异常值 | 使用BigInt防止数值溢出 |
扩展知识
- 性能监控集成:将数据推送到Prometheus/Grafana等监控系统
- 分布式追踪:结合OpenTelemetry实现全链路追踪
// 添加TraceID示例 res.setHeader('X-Trace-ID', generateTraceId()); - 中间件顺序:该中间件应放在所有路由之前但错误处理之后
- 替代方案:使用现成库如
response-time(源码类似本实现)