浏览器明明有缓存,为什么还在发请求:理解Cache-Control与ETag
打开开发者工具时,经常会看到一种现象:图片没有变化,浏览器却又发了一次请求。有人因此判断“缓存没有生效”,随后把缓存时间调得很长。结果新版本发布后,用户又迟迟看不到更新。
这里混在一起的,其实是两个问题:浏览器现在能不能直接使用已有内容,以及它是否需要向服务器确认内容有没有变化。理解这两道判断,才能把缓存配置放在合适的位置。
服务器第一次返回资源时,可以附带这样的响应头:
HTTP/1.1 200 OK
Cache-Control: public, max-age=600
ETag: "asset-v7"
Content-Type: text/css
max-age=600 表示资源的缓存新鲜期为六百秒。浏览器会结合响应中的日期、年龄等信息计算当前年龄;刚取得且年龄接近零的响应,在这段时间内通常可以直接复用。此时网络面板可能显示从内存或磁盘缓存读取,服务器也就不必再次传输文件。
超过新鲜期,并不代表文件一定发生了变化。浏览器保存了 ETag,就可以发送条件请求,让服务器判断它持有的版本是否仍然有效:
GET /assets/app.css HTTP/1.1
If-None-Match: "asset-v7"
如果版本一致,服务器可以返回 304 Not Modified。这次响应不包含资源正文,浏览器继续使用本地内容。因此,看到请求并不能直接认定缓存失效:协商缓存省下的是正文传输,仍然需要一次网络往返。
另一个常见误解来自 no-cache。它允许保存响应,但通常要求复用前先向服务器验证。它适合希望及时确认更新、同时还想保留协商能力的资源。真正表达“不要存储”的指令是 no-store。对带有用户私密数据的响应,还应根据业务设置 private 或更严格的存储策略,避免让共享缓存复用个人内容。
实际项目可以先按资源的更新方式划分。带内容指纹的静态文件,例如 app.a81f9c.js,内容改变时文件名也改变,适合较长的新鲜期,并配合 immutable。入口 HTML 往往需要尽快取得新版本,可以使用 no-cache 并配置有效的 ETag。这套安排成立的前提是发布过程中保留必要的旧静态资源,否则仍在访问旧页面的用户可能遇到文件不存在。
不要简单地给所有接口都设置 public。响应如果受登录身份、语言或请求头影响,缓存必须正确区分不同版本。某些差异可以通过 Vary 表达,但它不能替代对个人信息的缓存边界设计。CDN、网关和浏览器也可能有各自的规则,排查时要沿着实际请求链检查。
ETag 也需要代表真实的内容版本。如果所有响应都使用一个固定值,服务器可能错误地告诉客户端“没有更新”。如果响应经过压缩或其他变换,生成和比较标识时还需要遵循服务端框架对不同表示的处理方式。优先使用框架已经实现的机制,避免随意拼一个时间戳冒充版本管理。
验证配置时,可以临时把新鲜期设成十秒,关闭开发者工具里的“禁用缓存”,连续访问同一资源。先观察新鲜期内是否复用缓存,再等待过期,检查请求是否携带条件头、服务器是否返回 304。最后修改资源内容,确认客户端能收到新的正文和版本标识。
强制刷新会改变浏览器的缓存行为,不能只用这一种操作判断正常访问是否正确。把“直接复用”“先验证再复用”“重新下载”三种情况分别观察清楚,缓存问题通常就会从一组看似神秘的状态码,变成可以逐步验证的配置问题。
- 点赞
- 收藏
- 关注作者
评论(0)