症状
本篇调试指南针对“HTTP If-None-Match 与弱 ETag — 意外返回 200”。内容涵盖可观察的症状、可复现的诊断路径、根本原因、具体修复方式,以及如何验证修复并防止回归。
可观察到的症状包括:
- 带 If-None-Match 的条件 GET 总返回 200 而非 304
- 对相同内容每次请求 ETag 都变
- CDN 缓存陈旧响应体,而源站重验证返回 200
诊断
先在隔离环境中复现故障,再按顺序执行以下检查,逐步缩小范围。
步骤 1
curl -sI https://api.example.com/items | grep -i etag; sleep 1; curl -sI https://api.example.com/items | grep -i etag
相同内容得到不同 ETag,说明生成方式不确定。
步骤 2
curl -i -H 'If-None-Match: "abc123"' https://api.example.com/items
304 即确认匹配生效;200 说明服务端未正确比较。
常见诱因(按出现频率排序):
- ETag 源自时间戳或随机值,因而从不稳定
- 涉及 W/ 前缀时强比较与弱比较处理错误
- Vary 或 Cache-Control 不一致,导致缓存与源站判断相左
根因
If-None-Match 对 ETag 使用弱比较,仅当当前表示匹配时才返回 304。非确定性的 ETag 或比较模式不匹配会导致无法匹配,服务端便总是返回 200。
修复
用稳定的内容哈希生成 ETag,为 If-None-Match 遵循弱比较,让 Cache-Control 与验证器一致,并在响应依赖请求头时包含 Vary。
注意:弱 ETag 允许语义等价但字节不同的表示;在需要字节精确的 Range 请求处不可使用。
import { createHash } from 'node:crypto';
const body = JSON.stringify(items);
const etag = 'W/"' + createHash('sha256').update(body).digest('hex').slice(0, 16) + '"';
if (req.headers['if-none-match'] === etag) {
return res.status(304).end(); // representation unchanged
}
res.setHeader('ETag', etag);
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
res.type('application/json').send(body);
验证
把返回的 ETag 回传于 If-None-Match 并确认 304;修改内容并确认 ETag 变化且返回 200。
修复后补充一条回归测试,复现原始症状,防止问题再次出现。
这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。