记录下http常见缓存,比如常说的协商缓存,强缓存 …
概述
缓存的种类可分为两大类,私有缓存
、共享缓存
- 共享缓存:共享缓存可以被多个用户使用,比如为特定的用户提供热门资源缓存,缓存服务器等。
- 私有缓存:私有缓存只能用于单独用户,这里主要介绍浏览器http缓存
http缓存就是避免相同的资源反复加载,用来提高web程序的响应性能。
常见的HTTP缓存只能存储 GET
响应,缓存内容由请求地址(URI
)和方法(request method),针对一些特定的请求,也可以通过关键字区分多个存储的不同响应以组成缓存的内容,参考:Vary
按照日常使用,把缓存配置分为 强缓存
和 协商缓存
逐个介绍,如下。
强缓存包括: Expires
、Cache-control
协商缓存包括: ETag
、Last-Modified
Cache-control
Cache-control
由 HTTP1.1
配置,请求头和响应头都支持这个属性。
可缓存性
no-store
- 禁止缓存数据到本地
no-store 不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。
需要完全禁用缓存的时候设置
no-store
即可。
no-cache
- 缓存但重新验证
no-cache 响应可以存储数据到本地,但使用缓存前需要和源服务器校验缓存有效性(协商缓存验证)。
如果在浏览器(非支持bfCache浏览器)前进后退时,页面资源请求也会直接使用已有缓存,
no-store
没存储缓存所以并不会。
public
- 可以被任何缓存器缓存
public 表明响应可以被任何缓存器缓存比如(代理服务器,cdn等),即使是通常不可缓存的内容
private
- 只能被是有缓存器缓存
private 也就是只能被浏览器缓存。
如果不想被 任何缓存器缓存需要使用
no-store
过期时间
max-age
- 资源被视为新鲜的最长时间
通过 max-age=<seconds>
设置缓存有效时间(最大新鲜时间)秒
,通常应用于页面中不可更改的文件,比如js 、css、图片等静态资源。
与 Expires 指令不同,该指令是相对于请求时间的差值
s-maxage
同 max-age 一样表示缓存有效时间,优先级高于 覆盖 max-age 或 Expires,但仅适用于共享缓存(例如,代理、cdn等),会被私有缓存忽略。
max-stale
表明客户端愿意接收一个已经过期的资源,设置一个秒值,表示可接受最大过期时间。
为缓存续命,相当于 fresh-time(缓存时间) = max-age + max-stale
min-fresh
表示客户端希望获取一个能在指定的秒数内依旧保持其最新状态的响应。
为缓存减命,相当于 fresh-time(缓存时间) = max-age - min-fresh
stale-while-revalidate
- 扩展指令
表明客户端愿意接受陈旧的响应,同时在后台异步重新校验缓存。设置一个秒值,表示可接受最大过期时间。
例:
1 | Cache-Control: max-age=600, stale-while-revalidate=30 |
表示缓存在600s内新鲜,如果过期不超过30s,则缓存仍然可使用并且同时去和服务器重新校验,超过30s之后 真正
时效,需要再次和服务器重新校验。
保证资源尽量使用缓存的同时,又保证了缓存最多一次的延后,Workbox 也提供该缓存策略
stale-if-error
- 扩展指令
表示缓存校验失败,则客户愿意接受陈旧的响应。设置一个秒值,表示可接受最大过期时间。
重新校验生效
must-revalidate
指示一旦缓存过期,就必须与源服务器校验缓存有效性,必须校验之后才能使用。
看到这里是不是挺疑惑的,缓存本来就是要校验有效性,这里为啥还需要这样一个属性?
must-revalidate
生效有个前提,就是这个缓存必须已经过期,缓存过期之后就会重新 revalidate
为啥还需要一个 must-revalidate
,这是因为http规范在某些特殊情况可以直接使用过期的缓存,比如(stale-while-revalidate、stale-if-error 等)
带有 must-revalidate
的缓存,在任何情况下,都必须成功 revalidate
后才能使用,没有例外。
参见这里,详细解释了为啥需要
must-revalidate
: https://zhuanlan.zhihu.com/p/60357719
proxy-revalidate
类似于 must-revalidate
,但仅用于共享缓存(例如代理)。被私有缓存忽略。
immutable
- 扩展指令
表示响应正文不会随时间而改变,该资源(如果未过期)在服务器上不变,因此,即使用户显式刷新页面,客户端也不应为其发送条件重新验证。
Firefox 要求必须为https
Pragma
Pragma
由 HTTP/1.0
支持配置。
只支持 no-cache
配置,如果不配置 Cache-Control
,行为与 Cache-Control: no-cache
一致,主要在一些需要兼容 HTTP/1.0
的场景中使用。
例如:Chrome 强制刷新浏览器
1 | Cache-Control: no-cache |
Expires
HTTP1.0
支持配置。
缓存过期时间,如果 Cache-Control
配置了 max-age
或者 s-max-age
那么 Expires
将被忽略。
例:1
Expires: Wed, 21 Oct 2015 07:28:00 GMT
使用 GMT 时间格式,且时间会受到客户端时间的影响,所以假如用户本地时间有误可能会导致缓存异常,max-age 使用时间差值相隔秒数,避免了这种问题
Last-Modified
HTTP1.0
支持配置。
response
头,内容为服务器资源修改的日期及时间,使用 GMT 时间格式
当请求过服务器之后,服务器返回了 Last-Modified
头,下次需要重新校验的时候浏览器会发送 If-Modified-Since
(当前缓存的最后修改时间)请求头。
然后服务器对比 If-Modified-Since
时间和当前资源的 Last-Modified
,看文件是否有变更,变更过会返回 200
重新接收新资源和缓存策略,否则没变更过返回 304
直接使用当前缓存。
使用 Last-Modified
存在一些不能很好使用缓存的缺陷:
- 如果资源是服务端动态输出的非静态资源那修改时间就永远等于当前时间了,或者定期的重新生成内容,CDN多个节点时间不一致等,都会导致更新时间变更
- 时间控制的精度是只能到秒级
Etag 有效避免了这些问题
启发式缓存
条件:如果 max-age
和 expires
属性都没有,但存在 Last-Modified
缓存的新鲜时间就等于头里面 Date
的值减去 Last-Modified
的值除以10(注:根据rfc2626其实也就是乘以10%),其实就是 文件最近一次更新到现在的十分之一时长作为可缓存时长 。
也就是 (date_value - last_modified_value) * 0.10
,但这个只是推荐的规范,各个浏览器的实现可能会有差别。
如下:Chrome 中触发启发式缓存,没有设置强缓存也会响应 200
form disk cache
Cache-Control: no-cache
或者max-age=0
可以禁用启发式缓存,但在 Chrome 中,Cache-Control: must-revalidate
也有同样的功能。
Etag
HTTP1.1
支持配置。
Etag
和 Last-Modified
类似,区别在于判断文件的变更不是使用变更时间,而是通过内容指纹,来解决了 Last-Modified
的缺陷。
如果同时设置
Etag
和Last-Modified
那需要同时满足新鲜度校验才会返回304
Vary
Vary
是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。
它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers)
通俗点讲:
首先常见HTTP缓存只能存储 GET 响应,缓存内容由请求地址(URI)和方法(request method)确定,如果需要一个 URI
提供不同的内容,那就需要一个 key
来区别标识,这个就是通过 Vary
来配置。
通过 Vary
可以针对一个请求地址(URI)来提供多份内容。只有当 Vary 配置的头,在当前请求和原请求缓存都匹配时,才可以使用缓存,这个过程就是 content negotiation(内容协商)
- Client1 请求缓存服务器
Accept-Encoding = *
, 没有缓存再继续请求源服务器- Client2 请求缓存服务器
Accept-Encoding = br
,缓存不匹配,请求源服务器返回了Vary: Accept-Encoding
,就是告诉缓存服务器下次相同的请求也使用Accept-Encoding = br
请求头可以使用缓存- Client3 请求缓存服务器
Accept-Encoding = br
,命中了缓存,使用缓存返回给客户端而不再请求源站
例如:
1 | Vary: <header-name>, <header-name>, ... |
Vary: *
表示总是不匹配,请求都会到达源站服务器。
对于浏览器来说,浏览器并不会对 Vary 存储多个副本,而只是通过 Vary 做缓存校验
CORS和Vary
如果服务器 Access-Control-Allow-Origin
未使用 *
,而是指定了一个域,那么为了向客户端表明服务器的返回会根据 Origin
请求头而有所不同,必须在 Vary
响应头中包含Origin。1
2Access-Control-Allow-Origin: https://developer.mozilla.org
Vary: Origin
浏览器行为
Command + R
Chrome、firefox 都会在页面 html文档
的 request
请求头加上 Cache-Control max-age=0
,也就是重新校验缓存。
但: firefox 不仅会对 html 文件加max-age=0,也会对页面内的资源请求加 max-age=0
Command + Shift + R
firefox、Chrome 都使用request
请求头加上 Cache-Control: no-cache
、 Pragma: no-cache
来强制重新请求服务器。
区别:
- Chrome - 只会对部分资源添加
no-cache
。
体验上像是只对在页面
load
之前所有的请求加上如上参数,其它的异步资源
(包含:xhr请求,manifest.json,图片延迟加载,onload事件之后导入的异步脚本等等)HTTP2 Server Push 资源
都不会添加继续使用缓存。
- firefox - 会绝大部分资源都加上
no-cache
体验上看也有例外,比如:
HTTP2 Server Push 资源
、manifest.json
内的图片请求还是会使用缓存,同时有些在 onload 之后加载的脚本也会使用缓存
network disabled cache
浏览器控制台禁用缓存,类似强制刷新,但对所有资源都会请求都会加上 no-cache
,包括 PWA manifest
,HTTP2 Server Push
,异步资源
等等
这里 Chrome 和 firefox 表现一致。
浏览器前进/后退
支持 bfCache
的浏览器会永远使用缓存且脚本不重新执行。
不支持的除非配置 no-store
否则也是使用缓存。
其它
bfCache
当你浏览器前进后退页面完全不刷新,且js的执行状态都是保持的,这个特性就是 BF Cache
,全称 Backward/Forward Cache
,可以在 safari 浏览器中体验,Chrome 目前默认不开启这个。
由于不刷新且完全保留上个页面的js执行状态,所以页面绝对缓存,即使 no-store
可以通过 window pageshow
事件来监听页面前进后退行为。
另外在 Chrome浏览器 中前进后退的操作时,页面资源也不会重新请求,除非显示配置了
Cache-control
:no-store
max-age=0 vs no-cache 区别
response
中no-cache
、max-age=0
,以及request
中的max-age=0
都是一样的,表示缓存过期(不新鲜)需要重新进行校验。request
中的no-cache
则表示要重新请求服务器资源,如:浏览器在强制刷新的时候使用该方式强制请求服务器资源。