HTTP 协议中,Last-Modified
/ If-Modified-Since
和 ETag
/ If-None-Match
是用于 缓存验证 的两个重要机制。它们帮助客户端(如浏览器)判断本地缓存是否仍然有效,从而减少不必要的数据传输、提升性能。
🔍 1. Last-Modified
/ If-Modified-Since
✅ 工作原理:
-
服务器响应头:
Last-Modified
- 表示资源最后一次修改的时间(GMT 格式)。
- 第一次请求时,服务器返回这个头。
-
客户端请求头:
If-Modified-Since
- 客户端下次请求同一资源时带上这个头,值是上次收到的
Last-Modified
时间。 - 服务器根据这个时间判断资源是否更新:
- 如果未修改 → 返回
304 Not Modified
- 如果已修改 → 返回
200 OK
和新内容
- 如果未修改 → 返回
- 客户端下次请求同一资源时带上这个头,值是上次收到的
📦 示例:
第一次响应:
HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Wed, 25 Jun 2025 12:00:00 GMT
第二次请求:
GET /index.html HTTP/1.1
Host: example.com
If-Modified-Since: Wed, 25 Jun 2025 12:00:00 GMT
⚠️ 局限性:
- 精度为秒级,可能无法检测到秒内的多次修改。
- 文件系统时间戳问题:某些情况下文件被重新部署但修改时间不变。
- 负载均衡或 CDN 场景下可能出现不一致。
🔍 2. ETag
/ If-None-Match
✅ 工作原理:
-
服务器响应头:
ETag
- 表示资源当前状态的一个唯一标识符(字符串),通常基于内容哈希、版本号等生成。
- 每次资源变化,ETag 也会改变。
-
客户端请求头:
If-None-Match
- 客户端下次请求带上这个头,值是上次收到的 ETag。
- 服务器对比 ETag:
- 如果相同 → 返回
304 Not Modified
- 如果不同 → 返回
200 OK
和新内容
- 如果相同 → 返回
📦 示例:
第一次响应:
HTTP/1.1 200 OK
Content-Type: text/html
ETag: "abc123"
第二次请求:
GET /index.html HTTP/1.1
Host: example.com
If-None-Match: "abc123"
✅ 优势:
- 更精确地识别资源变更,可以做到字节级别差异检测。
- 不依赖时间戳,避免了时间同步问题。
- 支持弱 ETag(
W/"abc123"
),表示语义等价而非字节完全一致。
🔄 对比总结
特性 | Last-Modified / If-Modified-Since | ETag / If-None-Match |
---|---|---|
精度 | 秒级 | 字节级(更精确) |
唯一性 | 基于时间戳,可能重复 | 唯一标识符,每次变更都会变 |
性能开销 | 较低(只需比较时间) | 略高(需计算哈希等) |
兼容性 | 所有 HTTP/1.0 及以上支持 | 需要 HTTP/1.1 或以上 |
适用场景 | 简单静态资源缓存验证 | 动态资源、CDN、负载均衡环境 |
💡 实际使用建议
-
优先使用
ETag
,尤其在以下情况:- 资源可能会在一秒内多次变更。
- 使用 CDN 或反向代理集群。
- 动态生成的内容(如 API 响应)。
-
结合使用:很多服务器会同时发送
Last-Modified
和ETag
,以提供双重验证机制。 -
弱 ETag (
W/"..."
):适用于内容语义等价即可(比如 HTML 内容格式化变了但语义没变)。
🛠️ 在 Express 中设置 ETag 示例
const express = require('express');
const app = express();
// 默认开启 ETag
app.get('/data', (req, res) => {
res.header('ETag', '"custom-etag"');
res.send('Hello World');
});