缓存可以询问已存储的副本是否仍可使用,而不必每次重新下载正文。它发送验证器;匹配的条件 GET 可以收到 304 Not Modified,表示变化时通常需要新的 200 响应。验证是在命中时省下正文,并非每次请求都省下。

缓存把弱 ETag W/"release-a" 原样放入 If-None-Match。它与源站标签 "release-a" 弱比较匹配,源站返回无正文的 304;缓存更新元数据并复用正文 A。另一种结果是源站标签 "release-b" 导致 GET 返回 200 与新正文 B。HEAD 使用相同状态判断,但不返回正文。弱匹配既不证明字节相同,也不证明新鲜度。
标签仅为示意;假定请求原本成功。GET/HEAD 使用弱比较;HEAD 无正文。匹配既不证明字节相同,也不证明新鲜度。其他方法通常返回 412;If-Range 不能使用弱 ETag。

验证器类型与强度是两回事

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 还需要适用的缓存元数据,让缓存更新已存储的响应;它不只是一个空的成功状态。

来源