缓存可以询问已存储的副本是否仍可使用,而不必每次重新下载正文。它发送验证器;匹配的条件 GET 可以收到 304 Not Modified,表示变化时通常需要新的 200 响应。验证是在命中时省下正文,并非每次请求都省下。
验证器类型与强度是两回事
ETag 是带双引号的不透明值,可以是强标签("release-a"),也可以明确标记为弱标签(W/"release-a")。强比较要求两者都为强标签且值相等;弱比较忽略 W/ 标记,只比较不透明值。即使字节不同,弱标签也可以描述语义等价。
Last-Modified 是 HTTP 日期中精确到秒的时间戳。通常它是弱验证器,除非满足 RFC 9110 第 8.8.2.2 节的条件、可认定为强验证器。不能把所有 ETag 都归为强验证器,也不能无条件把所有时间戳归为弱验证器。
先前的站点观察记录了:
etag: "dkx84jwth7nkc9e"
last-modified: Mon, 24 Aug 2026 13:55:23 GMT
这些是历史值;这里未保留原始响应日志,本轮审阅也未重新检查在线结果。Caddy 文件服务器通常会设置这两个字段,但仍取决于文件元数据与配置。
条件 GET 使用弱比较
把收到的 ETag 原样放进 If-None-Match,保留双引号与可能存在的 W/。原笔记针对上述值报告过 HTTP 304,但展示的请求值没有双引号;该请求文本不符合实体标签语法,不应当作可工作的探针照抄。测试在线端点前应取得当前验证器。
下面无需套接字的练习,展示对单个、已解析标签的比较;它不是完整的 HTTP 头部解析器:
def weak_value(tag):
return tag[2:] if tag.startswith('W/') else tag
def strong_equal(a, b):
return not a.startswith('W/') and not b.startswith('W/') and a == b
stored = 'W/"release-a"'
current = '"release-a"'
assert weak_value(stored) == weak_value(current)
assert not strong_equal(stored, current)
assert weak_value(stored) != weak_value('"release-b"')
print("weak match: True; strong match: False; changed tag: no match")
对原本会成功的 GET 或 HEAD,If-None-Match 匹配时返回无正文的 304。其他方法上的同一前置条件为假时,通常返回 412 Precondition Failed。若同时存在 If-None-Match 与 If-Modified-Since,ETag 条件优先。通过 If-Range 做字节范围验证时,不能使用弱 ETag。
重新发布时不要保留误导性的验证器
ETag 不一定是内容哈希。Caddy v2.10.2 的默认文件标签由修改时间与大小生成;替换字节时若两者都保持不变,标签也可能不变。检查实际部署版本与覆盖配置,再让发布流程更新验证器依赖的输入,或采用基于内容的版本标识。
在发布清单中保留字节校验和,并测试未变化与已变化两种响应。校验和不会自动成为服务器的 ETag。304 还需要适用的缓存元数据,让缓存更新已存储的响应;它不只是一个空的成功状态。
来源
- RFC 9110 — HTTP Semantics,第 8.8 与 13 节的验证器强度及前置条件,已核验 2026-10-15。
- RFC 9111 — HTTP Caching,第 4.3 节的验证与已存储响应更新,已核验 2026-10-15。
- MDN: HTTP 条件请求,条件请求概览,已核验 2026-10-15。
- Caddy v2.10.2 文件服务器源码,
calculateEtag的输入与默认响应字段,已核验 2026-10-15。