HTTP 304 不是错误:解析 ETag 与浏览器缓存的重新验证

ネットワーク・RFCカテゴリを表すパンダのイラスト 网络・RFC

关于本文
本文是通过利用生成式 AI 的自动化工作流创建的。我们基于 HTTP 国际标准 RFC 9110、RFC 9111 以及 MDN 的第一手资料,梳理了 304 响应与缓存之间的关系。

验证状态:📘 已确认官方规范・未进行实际通信/浏览器操作

当使用开发者工具检查网站时,Network 列表中有时会显示“304 Not Modified”。由于没有接收到正文内容,但图像和样式却能正常显示,您可能会疑惑“这是通信错误吗”或“缓存损坏了吗”。304 并不是常规的故障,而是告知您可以继续使用之前保存的内容的响应。如果将其理解为避免传输冗余正文的机制,排查原因就会变得更容易。我们将通过具体示例跟踪由 ETag 和 If-None-Match 实现的重新验证,并梳理 304、200 以及无通信之间的区别。在其他文章中介绍过的 HTTP 428 和 412 主题是防止更新被覆盖,而本文的主题则是判定通过 GET 获取的显示资源是否需要重新获取。这一缓存判定过程。是否需要重新获取通过 GET 获取的显示资源。

项目对应环境与条件
对象HTTP GET/HEAD、浏览器或代理缓存
用于观察的工具Chrome、Edge、Firefox 等浏览器的 Network 开发者工具
所需费用无需额外的付费服务或 API 密钥
调查范围已确认 RFC 规则及 MDN 说明。未在特定网站上进行实际测量
主要制约对于不支持条件请求的资源,可能无法观察到304

首先尝试:在“Network”中查看304的前后情况

请将观察对象设为公开的图片、CSS等调查后不会产生问题的资源。无需记录带身份验证的页面、Cookie或可识别个人的标头并转载到文章中。以下三个步骤供读者尝试,并不意味着在本次写作环境中一定能复现成功。

  1. 在浏览器的开发者工具中打开“Network(网络)”面板。首先确保“Disable cache(禁用缓存)”处于关闭状态。以常规方式加载页面,并选择一个图片或CSS请求。

  2. 检查响应标头(Response Headers)中是否存在 ETag、Last-Modified、Cache-Control 等。区分状态码为 200 且接收了正文的初次加载,与可能使用已保存资源的再次加载。

  3. 在常规重新加载后,对比请求标头(Request Headers)的 If-None-Match 或 If-Modified-Since、响应的状态码以及传输的数据量。如果出现了 304,请确认它是以什么条件进行查询的。

并不一定会产生 304。如果浏览器判断已保存的版本是最新的,则可能不会向网络发送查询,而是显示“from memory cache”或“from disk cache”等。强制重新加载或禁用缓存会改变条件。相反,如果资源已更新,则会返回 200 和新的正文,因此切勿仅凭未出现 304 就断定发生故障这一点非常重要。

200 与 304:何时会收到什么内容

在首次 GET 请求中,服务器会返回 200 OK 以及图片或 CSS 的内容。此时会返回 ETag 等标识符和保存条件,如果可以缓存,客户端便能保存正文。一段时间后,当需要重新验证时,客户端可以通过条件 GET 查询“手头版本是否仍可使用”。

例如,如果初次响应中包含 ETag: "rev-7",则后续请求中可以指定 If-None-Match: "rev-7"。如果服务器将其与当前的表述进行核对,并判断条件一致且未发生更改,则会返回 304 Not Modified。能够选择已保存表述的缓存可以重复使用该正文。如果已更改,则可能会返回 200 和新的正文。

场景用于说明的虚拟通信结果
初次GET → 200,ETag: "rev-7"获取正文与标识符
再次确认GET、If-None-Match: "rev-7"查询手头的版本
版本相同304 Not Modified使用已保存的正文
新版本200 OK,新 ETag获取更新后的正文

304 并不意味着“该 URL 永远不变”。这意味着在当前的条件请求下,可以复用已保存的表述。服务器端的内容可能会在其他时间点发生变化,或者根据请求条件选择不同的表述。因此,仅凭 URL 简单对比 304 和 200 是不够的。

在 RFC 9110 中,304 被定义为对条件 GET 或 HEAD 的响应。304 本身没有用于显示的正文,但可能包含 Date、ETag、Vary、Cache-Control 等必要的标头。缓存遵循 RFC 9111 的规则,可以识别已保存的响应并更新相关标头。之所以无需接收新正文就能显示页面,正是因为利用了先前的内容。

ETag 和哈希值是一回事吗?

ETag 是服务器分配给表述的标识符。虽然有些实现的来源是文件内容的哈希,但它并不一定是 SHA 等哈希。服务器可以自由选择生成方式。如果武断地认为“因为值是字符串所以它就是文件的哈希”,就会在更新判断或复现实验的解释上出现偏差。

也存在以 W/ 开头的弱 ETag。弱比较并不能保证表述的字节完全相同。在缓存重新验证中,某些场景会涉及弱比较,但如果要用于证明文件完全一致的用途,则需要谨慎判断。我们在本例中使用的 "rev-7" 仅是为了解释机制而设的虚拟值,并非真实网站的记录。

Last-Modified 是服务器指示的最后修改日期和时间。客户端有时会在 If-Modified-Since 中放入时间并发送条件 GET。然而,由于日期时间会受到精度和更新管理的影响,它与 ETag 并不完全是相同的信息。

如果请求中包含两个条件,RFC 9110 规定:优先使用 If-None-Match,并忽略 If-Modified-Since规定了这一点。这并不意味着将两个条件相加。请检查请求标头(Request Headers)中附带的是哪一个,从而区分是什么构成了判定依据。

机制客户端条件检查目标
ETagIf-None-Match比较已保存的表述与标识符
更新时间If-Modified-Since指定时间之后的更新
304对条件式 GET/HEAD 的响应是否可以复用已保存的正文

即使是304却无法获取文件的情况

常见浏览器具备保存先前响应并加以复用的机制。但是,如果自定义的 HTTP 客户端手动添加了 If-None-Match,即使从服务器收到了 304,客户端自身也可能不存在已保存的图像。由于 304 不包含图像本身,因此仅将该响应保存到文件并无法还原原本的图像。

这并不意味着服务器发送了空文件,而是未满足手头有可复用正文这一前提的问题。如果使用不带缓存的工具发送条件式 GET,则需要根据目的通过无条件 GET 获取实体。重试方法应符合客户端的规范,请勿无限制地重复相同的条件式请求。

即使浏览器显示的 304 传输量很小,用户收到的页面也是由先前保存的 HTML 或 CSS 构成的。因此,“传输字节少”和“用户端没有数据”是两回事。如果操作出现问题,请不仅检查 HTTP 状态码,还要确认是否选择了缓存中保存的表述。

在存在 CDN 或反向代理的架构中,浏览器与中继层、中继层与源站之间可能会运行不同的缓存判定。即使在浏览器中看到了 304,也未必一定是到达源站的请求的结果。明确观测点(即正在查看哪个网络或日志)非常重要。

no-cache 并非禁止保存

Cache-Control 中的 no-cache 是一个名字容易让人产生误解的设置。no-cache 并非绝对禁止保存,而是表示在重复利用已保存的响应之前必须进行验证。 如果重新验证的结果为 304,则有时会使用已保存的正文。认为有 no-cache 就不可能有 304 的理解是错误的。

另一方面,no-store 是禁止保存的指令。如果对包含该指令的资源期望通过已保存的正文进行重新验证,有时可能无法如预期般返回 304。若根据 max-age 等条件保存的版本仍然新鲜,则有时会不进行重新验证通信而直接复用。

条件典型行为容易误解的要点
新鲜的缓存可能会不进行通信而直接复用即使没有 304 也属于正常
no-cache在复用前进行验证并非禁止保存
no-store不保存响应与 no-cache 不同
需要验证的已保存版本可能是 304 或 200不一定总是 304

浏览器开发者工具中的“禁用缓存”(Disable cache)并不是与此类服务器 Cache-Control 具有相同含义的 HTTP 标头。它是浏览器侧用于改变开发中观察方式的功能。即使“勾选后 304 消失了”,也不能断定服务器的缓存配置出了故障。

CSS 无法更新时的排查方法

如果本应更新的 CSS 看起来依然是旧的,一开始就删除全部缓存有时会抹去问题的原因证据。首先,记录目标文件的 URL、状态(Status)、请求标头(Request Headers)中的条件标头,以及响应标头(Response Headers)中的 ETag 和 Cache-Control。在采用带版本号 URL 等形式的网站上,还要确认请求目的地是否已切换为新的 URL。

接下来,区分是根本没有发生通信、通过 304 复用了保存的版本,还是返回了带有更新版本的 200。如果没有通信,则可能是缓存依然新鲜。如果是 304,则确认客户端提供的标识符与服务器当前表现形式之间的对应关系。如果是 200,则检查是否收到了新的正文,或者检查 CSS 的应用顺序以及其他文件的加载情况。

即使在收到“返回 304 但画面未更新”的咨询时,也要先弄清楚 304 对应的是哪个文件。是因为 HTML 返回 200 但只有 CSS 是 304,还是图像和脚本采用了不同的缓存条件,应对方式也会有所不同。与其将所有情况一概而论地称为缓存故障,不如针对每个资源进行判定,这样更能精确定位问题所在。

另一方面,每次都返回 200 也是正常的。其原因可能是根本没有制定缓存保存策略、没有条件请求,或者资源已更新。不要把“提高 304 的比例”作为唯一的改善目标,而应综合观察是否在合适的时间显示了正确的内容、是否存在不必要的传输。

在运维监控中不要将 304 视为错误

在 HTTP 状态分类中,3xx 被视为重定向系列,但 304 与通过常见的 Location 引导至其他 URL 的用途不同。它是服务器在收到条件 GET/HEAD 后,用来告知可以使用现有表现形式的响应。单纯将“200 以外即为失败”的监控可能会对正常的重新验证产生误报。

不过,如果 API 客户端不处理缓存,并且始终期望获得完整的 JSON 正文,那么 304 有时也会出乎意料。在这种情况下,在指责服务器之前,先确认客户端是否发送了条件标头,或者在获取时是否保留了已保存的正文。即使作为 HTTP 协议是正常的,如果应用程序的实现条件不一致,也会给用户带来问题。

在共享调查结果时,分别记录浏览器版本、资源类型、缓存禁用开关、目标 URL 的机密性以及观察到的请求和响应标头,将更容易复现问题。请勿直接公开包含 Cookie、身份验证令牌、内部主机名或个人信息的原始日志。

图解:304 响应中即使没有正文也能显示画面的原因

要理解 HTTP 304 的含义,通过通信新送达的内容与以前保存的内容需要区分开来。例如,假设在第一次 GET 图像时收到了 200 OK 和正文,并将其与 ETag 一起保存。为了在下次显示时确认是否能使用该保存版本,有时会发送带有 If-None-Match 的条件 GET。

sequenceDiagram
    participant B as ブラウザー
    participant C as 保存済みキャッシュ
    participant S as Webサーバー
    B->>S: 初回GET
    S-->>B: 200と本文とETag
    B->>C: 本文と条件を保存
    B->>C: 保存版を利用できるか照合
    C-->>B: 再検証が必要
    B->>S: If-None-Matchを付けたGET
    S-->>B: 304 本文なし
    B->>C: 保存済み本文を再利用
    C-->>B: 画像やCSSを表示

该图是用于解释其机制的虚拟通信。在实际的浏览器、缓存和 CDN 中,传输路径有所不同。服务器返回 304 但不发送新的正文,是因为该响应的前提是存在可用的保存版本。不过,这并不保证在没有保存版本、选择的表现形式不同或缓存损坏的情况下也能正常显示。

区分无通信、304 和 200 这三种情况

如果缓存中保存的资源仍然新鲜,有时根本不会通过网络进行查询。这与发生 304 通信的状态不同。需要注意的是,如果仅仅为了观察 304 而选择“禁用缓存”或强制重新加载,其条件会与正常使用时有所不同。

flowchart TD
    A["保存済み資源を表示したい"] --> B{"保存版を利用できるか"}
    B -->|"新鮮"| C["再検証通信なしで利用する場合"]
    B -->|"再検証が必要"| D["条件付きGETを送信"]
    B -->|"保存版なし"| E["通常GETで本文を取得"]
    D --> F{"サーバーの判定"}
    F -->|"変更なし"| G["304 保存済み本文を利用"]
    F -->|"変更あり"| H["200 新しい本文を取得"]

该图简化了判断过程,实际上它还受 Cache-Control、Vary 以及浏览器实现条件的影响。并不是出现 200 就说明有问题,也不是没有出现 304 就说明缓存无效。应当结合请求与响应标头、保存条件以及重新加载方法来一起进行判断。

具体化只有 CSS 过时的排查过程

假设在一个虚构的网站中,CSS 已经更新,但只有部分用户看到的是旧设计。首先,确认相关 CSS 的 URL,以及 Network 中的最终响应属于无通信、304 还是 200。如果是 304,则对比请求中的 If-None-Match 或 If-Modified-Since 与返回的 ETag 等条件。如果是 200,则检查返回的是否是期望的新正文。

如果在此时无条件地删除所有缓存,可能会丢失复现问题的条件。请首先记录发生时间、URL、条件标头、使用的浏览器以及重新加载的方法。如果有代理或 CDN,还要排查是哪里返回了已保存的资源。请勿将身份验证 Cookie 或签名 URL 等机密信息粘贴到报告中,而应通过读取来缩小原因范围,然后再决定是否有必要更改设置。

小结:304 是保存版本的利用确认

304 Not Modified 表示作为条件 GET/HEAD 的结果,判断认为可以重复使用已保存的表述。304 本身没有正文是符合规范的。结合查看 ETag、If-None-Match、Last-Modified、Cache-Control 以及是否存在已保存的正文,将有助于区分是在节约流量的状态还是实际出现了故障。

官方资料

本文是对官方规范与观察步骤的解说,并非采集了实际通信日志的复现报告。未确认在特定浏览器中的表现、CDN 或 WordPress 中的实际显示效果。

文档信息

文章??
HTTP 304 不是错误:解析 ETag 与浏览器缓存的重新验证
?布日期
更新日期
来源
https://papanda925.com/?p=18471&lang=zh

?可: ?于本站?有相??利的正文及原??表,除非?有?明,可依据 CC BY 4.0 使用。本文可能包含使用生成式AI?建或??的内容。若代??有?可声明,或?接的GitHub???定了?可,?代?以??可?准。引用内容、第三方?料、?片及商?不在本?可范?内。 使用政策

标题和URL已复制