XversePro 知识库 · 网络
浏览器先看缓存是否新鲜,过期了再拿 ETag 找服务器确认,没变就回 304。
结论:浏览器先问自己,再问服务器。缓存还新鲜就直接用,一个请求都不发,这就是常说的强缓存。过期了就带上 ETag 或修改时间去问服务器,没变就回 304,只省下响应体,这就是协商缓存。
强缓存和协商缓存是国内的叫法。RFC 9111 里对应的词是新鲜度(freshness)和验证(validation)。
HTTP 缓存规则现在由 RFC 9111 定义,它在 2022 年 6 月发布,取代了旧的 RFC 7234。条件请求和 304 状态码则写在同时发布的 RFC 9110 里。
判断分两步。第一步看缓存里的响应是否新鲜,新鲜就直接用。第二步是缓存过期后,拿验证器去问服务器。验证器就是能判断内容变没变的凭据,常见的是 ETag 和 Last-Modified 两个响应头。
缓存是否新鲜,只比较两个数:新鲜期有多长,响应已经存了多久。RFC 9111 第 4.2 节把它写成 response_is_fresh = (freshness_lifetime > current_age)。响应的年龄会参考 Age 响应头,它是发送方估算的秒数,从源服务器生成或验证这份响应时算起,见 RFC 9111 第 5.1 节。
新鲜期按下表从上往下找,用第一个能用的值。几个头同时出现时,max-age 优先于 Expires。
什么都没写时,缓存可以自己估一个值,叫启发式缓存。RFC 9111 第 4.2.2 节建议估值不超过距 Last-Modified 时间的一个比例,并举例说这个比例常取 10%。MDN 的缓存指南也建议所有响应都明确写 Cache-Control,不要依赖启发式缓存。
| 顺序 | 来源 | 新鲜期 | 说明 |
|---|---|---|---|
| 1 | s-maxage=N | N 秒 | 只对共享缓存(如 CDN)生效 |
| 2 | max-age=N | N 秒 | 私有缓存和共享缓存都适用 |
| 3 | Expires | Expires 减 Date | 用服务器时钟相减,减少两端时钟不准的影响 |
| 4 | 都没有 | 启发式估算 | 例如距 Last-Modified 时长的 10% |
表 1 新鲜期的取值顺序,依据 RFC 9111 第 4.2.1 节和第 4.2.2 节。
ETag 是服务器给某个版本内容打的标签,内容一变标签就变。浏览器下次请求时,把它放进 If-None-Match 请求头。服务器比对后发现没变,就回 304 Not Modified,不带响应体。
Last-Modified 是另一种验证器,记录内容的最后修改时间,配套请求头是 If-Modified-Since。两者同时出现时,RFC 9110 第 13.1.3 节要求服务器忽略 If-Modified-Since,因为 ETag 的判断更准确。
ETag 前面带 W/ 的是弱验证器,表示内容有小改动时标签可能不变,比如只精确到秒的时间戳生成的标签。RFC 9110 第 8.8.1 节说,弱验证器容易生成,但用于比较时远不如强验证器可靠。
304 不是空响应。RFC 9110 第 15.4.5 节要求它带上 200 响应本该有的 ETag、Cache-Control、Expires 等头,缓存据此刷新元数据,重新开始计算新鲜期。
最常被误读的是 no-cache。它不是不缓存,而是可以存,但每次使用前必须先找服务器验证,见 RFC 9111 第 5.2.2.4 节。真正不让存的是 no-store,见第 5.2.2.5 节。
private 和 public 讲的是谁可以存。private 只允许浏览器这类私有缓存保存,CDN 这类共享缓存不能存。public 明确表示可以缓存,即使响应在其他规则下原本不能被存。
| 指令 | 能不能存 | 用之前要不要验证 | 典型场景 |
|---|---|---|---|
max-age=N | 能 | N 秒内不用 | 普通静态资源 |
no-cache | 能 | 每次都要 | HTML 入口页 |
no-store | 不能 | 无缓存可验证 | 含隐私数据的响应 |
private | 只能存在私有缓存 | 看其他指令 | 带用户信息的页面 |
public | 能,含共享缓存 | 看其他指令 | 希望 CDN 缓存的资源 |
表 2 常用响应指令,依据 RFC 9111 第 5.2.2 节和 MDN Cache-Control 文档。
JS 和 CSS 常常更新。直接给 bundle.js 设很长的 max-age,用户可能一直拿旧文件。MDN 的缓存指南给出的做法是把版本号或内容哈希放进 URL,内容一变 URL 就变,旧缓存自然用不上。
这样静态资源可以设一年的 max-age,也就是 31536000 秒,再加 immutable 告诉浏览器刷新页面时也不必验证。HTML 入口页用 no-cache,每次都协商,保证能拿到新的资源地址。