HTTP 响应头中的 Cache-Control、Expires、Last-Modified、Etag 分别起什么作用?
这四个 HTTP 响应头主要用于控制浏览器的缓存机制。为了更好地理解它们,我们需要将它们分为两类:
- 强缓存(Freshness / Expiration): 决定浏览器是否不需要向服务器发送请求,直接从本地缓存读取。
- 涉及字段:
Expires、Cache-Control
- 涉及字段:
- 协商缓存(Validation): 当强缓存失效后,浏览器向服务器发送请求,询问缓存是否仍然有效(即文件是否修改过)。
- 涉及字段:
Last-Modified、Etag
- 涉及字段:
以下是详细的解释和对比:
一、 强缓存阶段 (决定是否发起请求)
如果强缓存生效,浏览器状态码通常显示为 200 (from memory cache) 或 200 (from disk cache),不会向服务器发送网络请求。
1. Expires (HTTP/1.0)
- 作用: 指定资源过期的绝对时间(服务器端的时间点)。
- 格式: GMT 格式的时间字符串,例如:
Expires: Wed, 21 Oct 2023 07:28:00 GMT。 - 原理: 浏览器拿本地系统时间与这个时间对比。如果本地时间小于
Expires,则直接使用缓存。 - 缺点:
- 它依赖于客户端的本地时间。如果用户修改了电脑时间,或者客户端与服务器时间不一致,会导致缓存判断错误。
- 这是 HTTP/1.0 的产物,现在主要用于向后兼容。
2. Cache-Control (HTTP/1.1)
- 作用: 用于控制缓存行为的通用首部,功能比
Expires强大且优先级更高。 - 常见指令:
max-age=3600:表示资源在 3600 秒(1小时)后过期。这是相对时间,解决了Expires的时间同步问题。no-cache:不要被名字误导。它表示使用缓存前必须向服务器确认(即跳过强缓存,直接进入协商缓存阶段)。no-store:禁止任何缓存,每次都必须从服务器下载完整的最新资源。public/private:决定资源是否可以被中间代理服务器(CDN)缓存。
- 优先级: 当
Cache-Control: max-age和Expires同时存在时,Cache-Control优先级更高。
二、 协商缓存阶段 (决定是否使用旧资源)
当强缓存失效(例如 max-age 到期了),浏览器会向服务器发起请求。服务器会根据以下字段判断文件是否真的修改了。如果未修改,返回 304 Not Modified(告诉浏览器继续用旧的);如果修改了,返回 200 OK 和新文件。
3. Last-Modified (基于时间)
- 作用: 服务器告诉浏览器该资源的最后修改时间。
- 配合请求头:
If-Modified-Since - 工作流程:
- 服务器响应头带上
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT。 - 缓存过期后,浏览器再次请求该资源时,请求头会带上
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT。 - 服务器检查:如果文件最后修改时间等于该时间,返回 304;如果文件更新了,返回 200 和新内容。
- 服务器响应头带上
- 缺点:
- 精度问题: 只能精确到秒。如果在 1 秒内文件被修改了多次,它无法感知。
- 内容未变时间变: 如果只是打开文件保存了一下(修改时间变了),但内容没变,服务器也会误判为文件更新了,导致重新传输。
4. Etag (基于内容/指纹)
- 作用: 服务器根据文件内容生成的唯一标识符(哈希值或指纹),例如
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4"。 - 配合请求头:
If-None-Match - 工作流程:
- 服务器响应头带上
Etag。 - 缓存过期后,浏览器再次请求,请求头带上
If-None-Match: "33a64df5..."。 - 服务器重新计算当前文件的 Hash 值进行对比:如果一致,返回 304;如果不一致,返回 200 和新内容。
- 服务器响应头带上
- 优点: 精度最高,只要内容变了 Etag 就会变。
- 缺点: 服务器计算 Etag 需要消耗 CPU 资源(尤其是大文件)。
- 优先级: 当
Etag和Last-Modified同时存在时,服务器优先校验Etag。
三、 总结与优先级流程
优先级对比
- 强缓存:
Cache-Control>Expires - 协商缓存:
Etag>Last-Modified
浏览器请求资源的完整流程
- 浏览器发起请求。
- 检查强缓存:
- 检查
Cache-Control(max-age) 或Expires。 - 如果未过期 -> 直接使用缓存 (200 OK, from cache)。
- 如果已过期 -> 进入下一步。
- 检查
- 发起网络请求(协商缓存):
- 请求头携带
If-None-Match(对应之前的 Etag) 和If-Modified-Since(对应之前的 Last-Modified)。
- 请求头携带
- 服务器处理:
- 先检查 Etag: 如果不匹配 -> 返回 200 OK + 新资源 + 新 Etag。
- 如果 Etag 匹配 -> 再检查 Last-Modified。
- 如果 Last-Modified 也不匹配 (极少见情况) -> 返回 200 OK。
- 如果都匹配 -> 返回 304 Not Modified (浏览器收到 304 后,从本地缓存读取数据渲染)。
一张表概括
| 字段 | 类别 | 依赖依据 | 优先级 | 优点 | 缺点 |
|---|---|---|---|---|---|
| Expires | 强缓存 | 绝对时间 | 低 | 兼容性好 | 受客户端时间影响 |
| Cache-Control | 强缓存 | 相对时间/指令 | 高 | 精准、功能多 | HTTP/1.1 支持 |
| Last-Modified | 协商缓存 | 修改时间戳 | 低 | 计算快 | 精度仅为秒,可能内容没变时间变 |
| Etag | 协商缓存 | 内容哈希/指纹 | 高 | 精度高,内容变才变 | 计算消耗服务器性能 |