← 中文资源

简体中文版本

HTTP If-None-Match 与弱 ETag — 意外返回 200

通过生成稳定的 ETag、正确使用弱比较,并保持 Vary 与 Cache-Control 一致,修复重验证总是返回 200 的问题。

阅读英文原文

症状

本篇调试指南针对“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。

修复后补充一条回归测试,复现原始症状,防止问题再次出现。

这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。