本文要点
如果你返回的响应内容因人而异,比如登录后显示的昵称、订单列表、个人设置,那么这份响应必须加private
public不是private的反义词,也不是“允许缓存”的通用开关
这是private最容易被忽略的一个作用点

本文目录
先分清两种缓存:私有缓存和共享缓存各存谁的响应
如果你在响应头里看到private或public,先要弄清它们各自在跟哪种缓存说话。
按MDN的HTTP缓存说明,缓存分两类:私有缓存是绑定到特定客户端的缓存,通常就是浏览器缓存,它存储的响应不与其他客户端共享;共享缓存位于客户端和服务器之间,可以存储能在用户之间共享的响应,进一步分为代理缓存和托管缓存。
这个区分决定了private和public的作用范围。private是在告诉共享缓存“这份响应不要存”,public是在告诉缓存“这份响应可以存,即使有Authorization头”。
一个自建情境(示例,不是对某个真实网站的记录):你在练习目录里维护一个自建页面,用本地或公开地址打开它,在开发者工具的Network面板里点开响应头,看Cache-Control这一行写的是private还是public。下面所有判断都围绕这一行展开。
个性化响应为什么必须加private:cookie不等于私有
如果你返回的响应内容因人而异,比如登录后显示的昵称、订单列表、个人设置,那么这份响应必须加private。
MDN的说明很直接:如果响应包含个性化内容并且你只想将响应存储在私有缓存中,则必须指定private指令。原因是,如果个性化内容存储在私有缓存以外的缓存中,其他用户可能能够检索到这些内容,这可能导致无意的信息泄露。
这里有一个容易踩的坑:个性化内容通常由cookie控制,但cookie的存在并不能表明它是私有的,因此单独的cookie不会使响应成为私有的。也就是说,你不能因为响应里带了Set-Cookie,就默认缓存会把它当成私有内容处理。
所以判断顺序是:先问这份响应是不是因人而异,如果是,就加private;不要用“有没有cookie”来代替这个判断。
public什么时候用:只在带Authorization头的响应需要被存储时
public不是private的反义词,也不是“允许缓存”的通用开关。
按MDN的说明,public值具有使响应可存储的效果,即使存在Authorization标头。备注里写得很清楚:只有在设置了Authorization标头时需要存储响应时才应使用public指令。否则不需要,因为只要给出了max-age,响应就会存储在共享缓存中。
这句话可以拆成两层。第一层:没有Authorization头时,你不需要public,响应照样能进共享缓存。第二层:有Authorization头时,public会让这份响应变得可存储,而如果这份响应是用基本身份验证做个性化的,public的存在可能会导致问题。
所以public的使用场景很窄:你确实希望一份带Authorization头的响应被共享缓存存储。如果你对此不确定,MDN给的建议是选择不带public的写法。
一个自建示例(练习数据,不是真实配置):
# 带Authorization头,且确实希望共享缓存存储
Cache-Control: public, max-age=31536000
# 不太确定时,不用public
Cache-Control: max-age=2592000注意这两行只是说明public的有无,不涉及max-age怎么算。
请求合并:即使写了no-cache,个性化响应仍可能被共享
这是private最容易被忽略的一个作用点。
MDN在“请求合并”一节说明:共享缓存主要位于源服务器之前,旨在减少到源服务器的流量。如果多个相同的请求同时到达共享缓存,中间缓存将代表自己将单个请求转发到源,然后源可以将结果重用于所有客户端。
关键句在这里:当请求同时到达时会发生请求合并,因此即使响应中给出了max-age=0或no-cache,它也会被重用。
也就是说,no-cache并不能阻止请求合并把一份响应发给多个客户端。如果响应是针对特定用户个性化的,并且你不希望它在合并中共享,则应添加private指令。
所以对个性化响应来说,private不是可选项,而是必须项。它和no-cache回答的是两个不同问题:no-cache管“用之前要不要验证”,private管“能不能进共享缓存”。
一个组合写法:no-cache加private各管一件事
如果你既要内容始终最新,又要防止个性化内容泄露给其他用户,MDN给出的组合是:
Cache-Control: no-cache, private这两个指令分工不同。no-cache强制客户端在重用任何存储的响应之前发送验证请求;private则防止响应被共享缓存存储。
MDN在“默认设置”一节也提到:如果服务实现了cookie或其他登录方式,并且内容是为每个用户个性化的,那么也必须提供private,以防止与其他用户共享。
需要划清边界:本文只讲private和public在私有缓存与共享缓存中的区别,以及个性化响应为什么要加private。不讲max-age怎么计算,不讲ETag验证细节,也不讲具体服务器怎么配置。上面出现的max-age数值只是资料原文中的示例,不是本文要教的计算方法。
检查结果:在响应头里确认private有没有生效
写完响应头之后,可以按下面几步做一次核对(以下为编辑整理的检查方向,不是对某个真实网站的测试记录):
1. 打开开发者工具的Network面板,刷新页面,点开目标请求。
2. 看Response Headers里的Cache-Control这一行,确认private是否出现在里面。
3. 如果这份响应是个性化的,确认它没有只写no-cache而漏掉private。
4. 如果这份响应带了Authorization头,确认public是不是你确实需要的,而不是顺手加上的。
一个自建情境(示例):你在练习目录里放一个自建页面,服务器返回的响应头分别写成下面两种,观察Network面板里Cache-Control这一行的差别。
Cache-Control: no-cacheCache-Control: no-cache, private这一步只用来核对响应头里有没有private,不据此推断任何真实网站的行为。
适用限制:private和public不能解决什么
private和public只回答“这份响应能不能进共享缓存”这一个问题。它们不能解决以下事情:
- 不能替代身份验证和访问控制。private防止的是缓存层面的泄露,不是接口层面的越权。
- 不能保证内容最新。要保证每次拿到最新内容,需要配合no-cache这类指令,本文不展开。
- 不能控制托管缓存的全部行为。MDN指出,托管缓存的特性因部署的产品而异,某些托管缓存可能故意忽略no-store指令,这种情况下不应视为不符合标准,而应仔细阅读所用托管缓存机制的文档。
- 不能删除已经存储的旧响应。MDN说明,no-store指令阻止存储响应,但不会删除相同URL的任何已存储响应。
本文只讲private和public这两个指令在私有缓存与共享缓存中的区别,以及个性化响应为何要加private。不讲max-age计算、不讲ETag验证细节、不讲具体服务器配置。
观看知识要点视频 18 秒
18秒无旁白要点导览,文字来自已审核正文;完整步骤、条件及参考资料见下方。
参考资料
以下原始资料提供本文依据;请结合日期、适用条件和产品说明查阅。
请结合原始资料的适用条件阅读。
返回浏览器工具 →