HTTP 缓存机制 ✅
本章节深入探讨 HTTP 缓存的各种策略、技术和最佳实践,帮助你全面理解 HTTP 缓存的工作原理和优化方法。
HTTP 缓存控制策略
答案
HTTP缓存策略是指浏览器和服务器之间在传输资源时如何使用缓存的方式,主要目的是减少网络传输的数据量,提高页面访问速度。
主要缓存策略
-
强缓存
- 通过设置 HTTP 头部中的
Expires
或Cache-Control
字段来指定资源在本地缓存的有效期 - 当资源未过期时,浏览器直接从缓存中读取,不会向服务器发送请求
- 显著提高页面访问速度
- 通过设置 HTTP 头部中的
-
协商缓存
- 当资源缓存时间已过期,浏览器向服务器发送请求检查资源是否有更新
- 如果没有更新,返回 304 状态码,告诉浏览器直接使用本地缓存
协商缓存实现方式:
-
Last-Modified / If-Modified-Since:
- 服务器返回资源时添加
Last-Modified
头部字段(资源最后修改时间) - 浏览器下次请求时在请求头添加
If-Modified-Since
字段 - 服务器检查时间是否一致,一致则返回 304,否则返回新资源
- 服务器返回资源时添加
-
ETag / If-None-Match:
- 服务器返回资源时添加
ETag
头部字段(资源唯一标识) - 浏览器下次请求时在请求头添加
If-None-Match
字段 - 服务器检查标识是否一致,一致则返回 304,否则返回新资源
- 服务器返回资源时添加
-
离线缓存
- 通过使用 HTML5 提供的 Application Cache API
- 将页面资源缓存在本地,使用户在没有网络连接时也能访问页面
- 已被 Service Worker 技术逐渐取代
-
Service Worker 缓存
- Service Worker 是在浏览器后台运行的 JavaScript 线程
- 可以拦截和处理浏览器发送的网络请求
- 将页面资源缓存在本地,提高访问速度和用户体验
- 提供更灵活的缓存控制策略
Expires 和 Cache-Control 的区别
答案
在 HTTP 强缓存策略中,Expires
和 Cache-Control
是两种设置缓存有效期的方式,它们有重要的区别:
Expires(HTTP/1.0)
特性 | 详情 |
---|---|
协议版本 | HTTP/1.0 的产物 |
时间类型 | 绝对时间(具体的日期时间) |
格式示例 | Expires: Thu, 31 Dec 2024 23:59:59 GMT |
工作原理 | 服务器指定资源的确切过期时间 |
主要缺点 | 依赖客户端和服务器的时间同步 |
Cache-Control(HTTP/1.1)
特性 | 详情 |
---|---|
协议版本 | HTTP/1.1 的产物 |
时间类型 | 相对时间(秒数) |
格式示例 | Cache-Control: max-age=3600 |
工作原理 | 指定资源从请求时间开始的有效秒数 |
主要优势 | 不依赖时间同步,更可靠 |
Cache-Control 常用指令
指令 | 作用 |
---|---|
max-age=<seconds> | 指定缓存的最大有效时间 |
no-cache | 必须与服务器验证后才能使用缓存 |
no-store | 完全禁止缓存 |
must-revalidate | 缓存过期后必须重新验证 |
public | 可以被任何缓存存储 |
private | 只能被私有缓存存储 |
主要区别对比
-
时间精度:
- Expires:绝对时间,容易受时间偏差影响
- Cache-Control:相对时间,更加可靠
-
优先级:
- 当两者同时存在时,Cache-Control 优先级更高
-
灵活性:
- Cache-Control 提供更多的控制选项和指令
-
兼容性:
- Expires:兼容性更好,支持旧版浏览器
- Cache-Control:现代浏览器的标准选择
最佳实践
建议同时设置两个头部以获得最大兼容性:
Expires: Thu, 31 Dec 2024 23:59:59 GMT
Cache-Control: max-age=3600
对于现代应用,优先使用 Cache-Control
,并根据需要添加 Expires
作为后备。
Application Cache API 缓存机制
答案
基本概念
Application Cache API(应用程序缓存)是 HTML5 标准中提供的一个用于离线缓存 Web 应用程序的技术。它可以将 Web 应用程序中的文件(包括 HTML、CSS、JavaScript 和图像等)保存到客户端浏览器中的缓存中,在没有网络连接的情况下,仍然能够访问应用程序。
工作原理
在 Application Cache API 中,通过在 cache manifest 文件中列出需要缓存的资源列表来实现离线缓存
。该文件必须以 .appcache
为后缀名,并且必须在 Web 服务器上进行访问。浏览器会下载该文件,并将文件中列出的资源文件下载到本地缓存中。当应用程序在离线状态下打开时,浏览器会自动从本地缓存中加载缓存的文件。
Cache Manifest 文件示例
CACHE MANIFEST
version 1.0.0
CACHE:
index.html
styles.css
script.js
image.jpg
NETWORK:
*
FALLBACK:
offline.html
配置节说明
节 | 作用 | 说明 |
---|---|---|
CACHE | 指定要缓存的资源 | 这些资源会在首次访问时下载并缓存 |
NETWORK | 指定需要网络连接的资源 | 使用 * 表示所有其他资源都需要网络 |
FALLBACK | 指定离线时的后备页面 | 当网络不可用时显示的替代内容 |
Application Cache API 的局限性
-
更新复杂
- 当更新 Web 应用程序时,需要手动清除客户端浏览器中的缓存才能生效
- 否则用户访问的仍然是旧版本的应用程序
-
请求方法限制
- Application Cache API 只能缓存 GET 请求
- 不支持 POST 等其他请求方法
-
缓存策略不灵活
- 缓存策略相对简单,不能进行细粒度控制
- 难以实现复杂的缓存逻辑
替代方案
由于 Application Cache API 的局限性,现代 Web 开发更推荐使用 Service Worker 技术,它提供了更强大、更灵活的缓存控制能力。
注意事项
- Application Cache API 已被标记为废弃(deprecated)
- 不推荐在新项目中使用
- 建议迁移到 Service Worker 技术
Service Worker 缓存机制
答案
Service Worker 是一种在浏览器后台运行的脚本,可以拦截和处理浏览器网络请求。它提供了比 Application Cache API 更灵活、更强大的缓存控制能力。
Service Worker 缓存工作流程
-
注册 Service Worker
- 通过在页面中注册 Service Worker,告诉浏览器使用 Service Worker 来处理网络请求
-
安装 Service Worker
- 一旦 Service Worker 被注册,浏览器会下载并安装它
- 在安装过程中,Service Worker 可以缓存静态资源(HTML、CSS、JavaScript文件等)
-
激活 Service Worker
- Service Worker 安装成功后被激活
- 在激活过程中,可以删除旧版本的缓存或执行其他操作
-
拦截网络请求
- Service Worker 被激活后,可以拦截浏览器发送的网络请求
-
处理网络请求
- 当 Service Worker 拦截到网络请求时,执行自定义逻辑
- 可以检查缓存中是否已存在该请求的响应,如果存在则直接返回缓存响应
- 否则将请求发送到服务器并缓存服务器的响应
-
更新缓存
- 如果缓存中的资源发生变化,Service Worker 可以自动更新缓存
- 可以在后台下载最新资源,并更新缓存中的文件
Service Worker 缓存实现示例
// 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/service-worker.js').then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope)
}, function (err) {
console.log('ServiceWorker registration failed: ', err)
})
})
}
// 安装 Service Worker
self.addEventListener('install', function (event) {
console.log('ServiceWorker install')
event.waitUntil(
caches.open('my-cache-v1').then(function (cache) {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/image.png'
])
})
)
})
// 激活 Service Worker,清理旧缓存
self.addEventListener('activate', function (event) {
console.log('ServiceWorker activate')
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(cacheName => {
return cacheName.startsWith('my-cache') &&
cacheName !== 'my-cache-v1'
}).map(cacheName => {
return caches.delete(cacheName)
})
)
})
)
})
// 拦截网络请求
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
console.log('ServiceWorker fetch from cache:', event.request.url)
return response
} else {
console.log('ServiceWorker fetch from network:', event.request.url)
return fetch(event.request).then(response => {
// 缓存网络响应
if (response.status === 200) {
const responseClone = response.clone()
caches.open('my-cache-v1').then(cache => {
cache.put(event.request, responseClone)
})
}
return response
})
}
})
)
})
Service Worker 缓存策略
策略 | 描述 | 适用场景 |
---|---|---|
Cache First | 优先使用缓存,缓存未命中时请求网络 | 静态资源 |
Network First | 优先使用网络,网络失败时使用缓存 | 动态内容 |
Stale While Revalidate | 使用缓存同时在后台更新缓存 | 需要快速响应的资源 |
Network Only | 始终从网络获取 | 实时数据 |
Cache Only | 始终使用缓存 | 离线应用 |
Service Worker 优势
-
更灵活的缓存控制
- 支持多种缓存策略
- 可以根据请求类型制定不同的缓存规则
-
离线支持
- 提供完整的离线功能
- 可以在无网络时提供缓存内容
-
后台同步
- 支持后台数据同步
- 网络恢复时自动同步数据
-
推送通知
- 支持推送通知功能
- 即使页面关闭也能接收通知
注意事项
- 需要编写 Service Worker 脚本来处理请求
- 需要将脚本注册到浏览器中
- 要考虑缓存策略,确保缓存数据与服务器数据同步
- 仅在 HTTPS 环境下工作(开发时可用 localhost)
Expires 和 Cache-Control 的区别
答案
在 HTTP 强缓存策略中,Expires
和 Cache-Control
是两种设置缓存有效期的方式,它们有重要的区别:
Expires(HTTP/1.0)
特性 | 详情 |
---|---|
协议版本 | HTTP/1.0 的产物 |
时间类型 | 绝对时间(具体的日期时间) |
格式示例 | Expires: Thu, 31 Dec 2024 23:59:59 GMT |
工作原理 | 服务器指定资源的确切过期时间 |
主要缺点 | 依赖客户端和服务器的时间同步 |
Cache-Control(HTTP/1.1)
特性 | 详情 |
---|---|
协议版本 | HTTP/1.1 的产物 |
时间类型 | 相对时间(秒数) |
格式示例 | Cache-Control: max-age=3600 |
工作原理 | 指定资源从请求时间开始的有效秒数 |
主要优势 | 不依赖时间同步,更可靠 |
Cache-Control 常用指令
指令 | 作用 |
---|---|
max-age=<seconds> | 指定缓存的最大有效时间 |
no-cache | 必须与服务器验证后才能使用缓存 |
no-store | 完全禁止缓存 |
must-revalidate | 缓存过期后必须重新验证 |
public | 可以被任何缓存存储 |
private | 只能被私有缓存存储 |
主要区别对比
-
时间精度:
- Expires:绝对时间,容易受时间偏差影响
- Cache-Control:相对时间,更加可靠
-
优先级:
- 当两者同时存在时,Cache-Control 优先级更高
-
灵活性:
- Cache-Control 提供更多的控制选项和指令
-
兼容性:
- Expires:兼容性更好,支持旧版浏览器
- Cache-Control:现代浏览器的标准选择
最佳实践
建议同时设置两个头部以获得最大兼容性:
Expires: Thu, 31 Dec 2024 23:59:59 GMT
Cache-Control: max-age=3600
对于现代应用,优先使用 Cache-Control
,并根据需要添加 Expires
作为后备。
Application Cache API 缓存机制
答案
基本概念
Application Cache API(应用程序缓存)是 HTML5 标准中提供的一个用于离线缓存 Web 应用程序的技术。它可以将 Web 应用程序中的文件(包括 HTML、CSS、JavaScript 和图像等)保存到客户端浏览器中的缓存中,在没有网络连接的情况下,仍然能够访问应用程序。
工作原理
在 Application Cache API 中,通过在 cache manifest 文件中列出需要缓存的资源列表来实现离线缓存
。该文件必须以 .appcache
为后缀名,并且必须在 Web 服务器上进行访问。浏览器会下载该文件,并将文件中列出的资源文件下载到本地缓存中。当应用程序在离线状态下打开时,浏览器会自动从本地缓存中加载缓存的文件。
Cache Manifest 文件示例
CACHE MANIFEST
version 1.0.0
CACHE:
index.html
styles.css
script.js
image.jpg
NETWORK:
*
FALLBACK:
offline.html
配置节说明
节 | 作用 | 说明 |
---|---|---|
CACHE | 指定要缓存的资源 | 这些资源会在首次访问时下载并缓存 |
NETWORK | 指定需要网络连接的资源 | 使用 * 表示所有其他资源都需要网络 |
FALLBACK | 指定离线时的后备页面 | 当网络不可用时显示的替代内容 |
Application Cache API 的局限性
-
更新复杂
- 当更新 Web 应用程序时,需要手动清除客户端浏览器中的缓存才能生效
- 否则用户访问的仍然是旧版本的应用程序
-
请求方法限制
- Application Cache API 只能缓存 GET 请求
- 不支持 POST 等其他请求方法
-
缓存策略不灵活
- 缓存策略相对简单,不能进行细粒度控制
- 难以实现复杂的缓存逻辑
替代方案
由于 Application Cache API 的局限性,现代 Web 开发更推荐使用 Service Worker 技术,它提供了更强大、更灵活的缓存控制能力。
注意事项
- Application Cache API 已被标记为废弃(deprecated)
- 不推荐在新项目中使用
- 建议迁移到 Service Worker 技术
Service Worker 缓存机制
答案
Service Worker 是一种在浏览器后台运行的脚本,可以拦截和处理浏览器网络请求。它提供了比 Application Cache API 更灵活、更强大的缓存控制能力。
Service Worker 缓存工作流程
-
注册 Service Worker
- 通过在页面中注册 Service Worker,告诉浏览器使用 Service Worker 来处理网络请求
-
安装 Service Worker
- 一旦 Service Worker 被注册,浏览器会下载并安装它
- 在安装过程中,Service Worker 可以缓存静态资源(HTML、CSS、JavaScript文件等)
-
激活 Service Worker
- Service Worker 安装成功后被激活
- 在激活过程中,可以删除旧版本的缓存或执行其他操作
-
拦截网络请求
- Service Worker 被激活后,可以拦截浏览器发送的网络请求
-
处理网络请求
- 当 Service Worker 拦截到网络请求时,执行自定义逻辑
- 可以检查缓存中是否已存在该请求的响应,如果存在则直接返回缓存响应
- 否则将请求发送到服务器并缓存服务器的响应
-
更新缓存
- 如果缓存中的资源发生变化,Service Worker 可以自动更新缓存
- 可以在后台下载最新资源,并更新缓存中的文件
Service Worker 缓存实现示例
// 注册 Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/service-worker.js').then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope)
}, function (err) {
console.log('ServiceWorker registration failed: ', err)
})
})
}
// 安装 Service Worker
self.addEventListener('install', function (event) {
console.log('ServiceWorker install')
event.waitUntil(
caches.open('my-cache-v1').then(function (cache) {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/image.png'
])
})
)
})
// 激活 Service Worker,清理旧缓存
self.addEventListener('activate', function (event) {
console.log('ServiceWorker activate')
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(cacheName => {
return cacheName.startsWith('my-cache') &&
cacheName !== 'my-cache-v1'
}).map(cacheName => {
return caches.delete(cacheName)
})
)
})
)
})
// 拦截网络请求
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
console.log('ServiceWorker fetch from cache:', event.request.url)
return response
} else {
console.log('ServiceWorker fetch from network:', event.request.url)
return fetch(event.request).then(response => {
// 缓存网络响应
if (response.status === 200) {
const responseClone = response.clone()
caches.open('my-cache-v1').then(cache => {
cache.put(event.request, responseClone)
})
}
return response
})
}
})
)
})
Service Worker 缓存策略
策略 | 描述 | 适用场景 |
---|---|---|
Cache First | 优先使用缓存,缓存未命中时请求网络 | 静态资源 |
Network First | 优先使用网络,网络失败时使用缓存 | 动态内容 |
Stale While Revalidate | 使用缓存同时在后台更新缓存 | 需要快速响应的资源 |
Network Only | 始终从网络获取 | 实时数据 |
Cache Only | 始终使用缓存 | 离线应用 |
Service Worker 优势
-
更灵活的缓存控制
- 支持多种缓存策略
- 可以根据请求类型制定不同的缓存规则
-
离线支持
- 提供完整的离线功能
- 可以在无网络时提供缓存内容
-
后台同步
- 支持后台数据同步
- 网络恢复时自动同步数据
-
推送通知
- 支持推送通知功能
- 即使页面关闭也能接收通知
注意事项
- 需要编写 Service Worker 脚本来处理请求
- 需要将脚本注册到浏览器中
- 要考虑缓存策略,确保缓存数据与服务器数据同步
- 仅在 HTTPS 环境下工作(开发时可用 localhost)
Date 与 Last-Modified 的区别
答案
在 HTTP 响应头中,Date
和 Last-Modified
是两个不同的时间字段,它们有着不同的含义和用途:
基本概念对比
字段 | 含义 | 时间含义 | 示例 |
---|---|---|---|
Date | 消息产生的时间 | 服务器生成响应的时刻 | Mon, 07 Oct 2024 12:34:56 GMT |
Last-Modified | 资源最后修改时间 | 服务器上该资源最后被修改的时间 | Mon, 01 Sep 2024 10:20:30 GMT |
详细区别分析
一、含义不同
-
Date:表示消息产生的时间。服务器用这个时间来标记响应报文的生成时间,它反映了服务器生成响应的时刻。例如,"Mon, 07 Oct 2024 12:34:56 GMT",这个时间是服务器根据其自身的时钟生成的。
-
Last-Modified:指示资源的最后修改时间。它表示服务器上该资源最后被修改的时间。比如,一个网页文件最后一次被编辑的时间就可以通过这个字段告知客户端。例如,"Mon, 01 Sep 2024 10:20:30 GMT"。
二、用途不同
Date 字段用途:
- 客户端可以根据这个时间来判断响应的新鲜度。例如,如果客户端本地有缓存,它可以通过比较缓存的时间和响应中的
Date
来确定是否需要使用缓存 - 用于计算响应的年龄等缓存相关的参数
Last-Modified 字段用途:
- 主要用于缓存控制。客户端在后续的请求中可以通过
If-Modified-Since
请求头将这个时间发送给服务器,询问服务器资源是否在这个时间之后被修改过 - 如果没有被修改,服务器可以返回一个状态码为 304(Not Modified)的响应,告知客户端可以使用缓存中的资源,从而减少传输的数据量和提高响应速度
- 有助于客户端判断资源是否已经过期,以便决定是否需要重新获取资源
三、在缓存机制中的作用
请求 → 服务器 → 响应头包含:
Date: Wed, 21 Oct 2024 14:28:00 GMT
Last-Modified: Wed, 21 Oct 2024 10:00:00 GMT
再次请求 → 客户端发送:
If-Modified-Since: Wed, 21 Oct 2024 10:00:00 GMT
服务器检查:
- 如果资源在 10:00:00 之后没有修改 → 返回 304 Not Modified
- 如果资源在 10:00:00 之后有修改 → 返回 200 及新的资源
四、实际应用场景
- Date:主要用于日志记录、缓存年龄计算、响应新鲜度判断
- Last-Modified:主要用于条件请求、协商缓存、增量更新检查
总结
Date
主要表示响应的生成时间,而 Last-Modified
表示资源的最后修改时间。它们在 HTTP 通信中起着不同的作用,共同为缓存控制和资源管理提供重要信息。
no-cache 与 no-store 的区别
答案
在 HTTP 缓存中,no-cache
和 no-store
是两种不同的缓存指令,它们有着本质上的区别:
基本概念对比
指令 | 是否允许缓存 | 验证要求 | 适用场景 |
---|---|---|---|
no-cache | 允许缓存,但需验证 | 必须与服务器验证 | 需要确保数据最新但允许缓存优化 |
no-store | 完全禁止缓存 | 无需验证,直接请求 | 高敏感数据,绝对不能缓存 |
一、no-cache
详解
含义:
- 当设置了
no-cache
指令时,这并不意味着不使用缓存 - 表示在使用缓存之前,必须先与服务器进行验证,以确定缓存的资源是否仍然有效
- 浏览器会向服务器发送条件请求(通常使用
If-Modified-Since
或If-None-Match
头部)
工作流程:
浏览器请求资源
↓
检查本地缓存(存在 no-cache 资源)
↓
发送条件请求到服务器验证
↓
服务器返回 304(未修改) → 使用缓存
服务器返回 200(已修改) → 使用新资源并更新缓存
使用场景:
- 需要确保获取到最新资源,但又不想每次都从服务器获取完整资源的情况
- 经常更新但更新频率不高的资源(如新闻页面、产品信息等)
- 需要根据用户特定请求参数生成资源的情况
二、no-store
详解
含义:
no-store
指令表示绝对不允许缓存资源- 浏览器接收到带有
no-store
指令的响应后,不会将资源存储在任何缓存中 - 包括浏览器缓存、代理服务器缓存等都不会存储该资源
- 每次请求都必须从服务器获取最新的资源
工作流程:
浏览器请求资源
↓
直接向服务器请求(不检查缓存)
↓
服务器返回资源
↓
使用资源但不存储到缓存
使用场景:
- 对安全性要求非常高的资源(如银行交易页面、支付信息)
- 包含敏感信息的页面
- 动态生成且内容变化频繁的资源
- 需要严格保证每次都获取到最新数据的资源
三、实际应用示例
# no-cache 示例
Cache-Control: no-cache
# 允许缓存,但每次使用前必须验证
# no-store 示例
Cache-Control: no-store
# 完全禁止缓存
# 组合使用
Cache-Control: no-cache, no-store, must-revalidate
# 最严格的缓存控制
四、选择建议
需求 | 推荐指令 | 原因 |
---|---|---|
静态资源需要定期更新 | no-cache | 可以利用缓存提升性能,同时保证数据最新 |
用户个人敏感信息 | no-store | 完全避免敏感数据被缓存 |
实时数据(股价、聊天等) | no-store | 确保每次都获取最新数据 |
API 接口响应 | 根据数据性质选择 | 静态数据用no-cache ,敏感数据用no-store |
总结
- no-cache:表示"可以缓存,但使用前必须验证",适合需要平衡性能和数据新鲜度的场景
- no-store:表示"绝对禁止缓存",适合高安全性要求的场景
根据不同的需求和安全级别,选择合适的缓存指令来控制资源的缓存行为。
ETag 值改变与文件内容
答案
如果 HTTP 响应头中的 ETag 值改变了,通常意味着资源(文件或其他内容)很可能发生了变化,但并不绝对意味着文件内容一定已经更改。
一、可能导致 ETag 变化但文件内容未更改的情况
1. 生成方式的变化
- 如果服务器更改了生成 ETag 的方式,即使文件内容没有变化,ETag 也可能不同
- 例如:服务器原本使用文件的最后修改时间戳作为 ETag,后来改为使用文件内容的哈希值,那么即使文件内容未变,ETag 也会改变
2. 服务器配置或逻辑变化
- 服务器的某些配置更改或业务逻辑变化可能导致 ETag 的生成与之前不同,而与文件内容本身的变化无关
- 比如:服务器在不同的环境中可能有不同的 ETag 生成策略,从开发环境切换到生产环境时,ETag 可能会改变,即使文件内容相同
3. 动态资源的非内容相关变化
- 对于动态生成的资源,如由服务器端脚本生成的网页,ETag 可能受到一些与内容无关的因素影响
- 例如:服务器的负载、请求的时间等因素可能导致动态资源的 ETag 变化,而实际生成的内容可能并没有改变
二、ETag 的作用和可靠性
1. 缓存验证机制
- ETag 主要用于缓存验证,客户端在后续请求中通过
If-None-Match
请求头将上次接收到的 ETag 发送给服务器 - 服务器比较 ETag 来判断资源是否发生了变化
- 如果 ETag 相同,服务器返回
304 Not Modified
,客户端可以使用缓存中的资源,节省带宽和提高响应速度
验证流程:
客户端首次请求 → 服务器返回资源 + ETag: "123abc"
↓
客户端缓存资源和ETag
↓
客户端再次请求 + If-None-Match: "123abc"
↓
服务器检查ETag:
- 相同 → 返回 304 Not Modified
- 不同 → 返回 200 + 新资源 + 新ETag
2. 相对可靠性
- 虽然 ETag 的变化不能绝对确定文件内容的更改,但在大多数情况下,它是一个比较可靠的指示
- 如果 ETag 发生了变化,客户端应该重新获取资源以确保得到最新的内容
三、ETag 生成策略
生成方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
文件哈希 | 内容变化敏感,准确 | 计算开销大 | 静态文件 |
修改时间+文件大小 | 计算快速 | 可能出现误判 | 大文件 |
版本号 | 简单易控制 | 需要手动管理 | 版本化资源 |
内容摘要 | 高准确性 | 动态计算开销 | 小文件 |
四、最佳实践建议
-
服务端:
- 选择适合的 ETag 生成策略
- 确保生成策略的一致性
- 避免不必要的 ETag 变化
-
客户端:
- ETag 变化时重新获取资源
- 正确处理 304 响应
- 结合 Last-Modified 使用
总结
ETag 值的改变不能确凿地证明文件内容一定更改,但它是一个重要的线索,提示客户端可能需要重新获取资源以验证其是否发生了变化。在实际应用中,应该将 ETag 作为一个有效的缓存验证机制,但不应完全依赖它来判断内容是否真的发生了变化。