HTTP缓存主要分为Last-Modified/Etag和Cache-Control/Expires
其中Cache-Control/Expires属于强缓存,Last-Modified/Etag属于协商(比较)缓存
1、Cache-Control
1)、服务器的缓存控制
- 浏览器发现缓存无数据,于是发送请求,向服务器获取资源
- 服务器响应请求,返回资源,同时标记资源的有效期
- 浏览器缓存资源,等待下次重用
服务器标记资源有效期使用的头字段是Cache-Control,里面的值max-age=30就是资源的有效时间,相当于告诉浏览器,这个页面只能缓存30秒,之后就算是过期,不能用
max-age是生存时间,时间的计算起点是响应报文的创建时刻(即Date字段,也就是离开服务器的时刻),而不是客户端收到报文的时刻,也就是说包含了在链路传输过程中所有节点所停留的时间
max-age是HTTP缓存控制最常用的属性,此外在响应报文里还可以用其他的属性来更精确地指示浏览器应该如何使用缓存:
- no-store:不允许缓存,用于某些变化非常频繁的数据,例如秒杀页面
- no-cache:可以缓存,但在使用之前必须要去服务器验证是否过期,是否有最新的版本
- must-revalidate:如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证
Cache-Control头部在响应中的值:
- must-revalidate:告诉客户端⼀旦缓存过期,必须向服务器验证后才可使⽤
- proxy-revalidate:与must-revalidate类似,但它仅对代理服务器的共享缓存有效
- no-cache:告诉客户端不能直接使⽤缓存的响应,使⽤前必须在源服务器验证得到304返回码。如果no-cache后指定头部,则若客户端的后续请求及响应中不含有这些头则可直接使⽤缓存
- max-age:告诉客户端缓存Age超出max-age秒后则缓存过期
- s-maxage:与max-age相似,但仅针对共享缓存,且优先级⾼于max-age和Expires
- public:表示⽆论私有缓存或者共享缓存,皆可将该响应缓存
- private:表示该响应不能被代理服务器作为共享缓存使⽤。若private后指定头部,则在告诉代理服务器不能缓存指定的头部,但可缓存其他部分
- no-store:告诉所有下游节点不能对响应进⾏缓存
- no-transform:告诉代理服务器不能修改消息包体的内容
服务器的缓存控制策略流程图:
![](https://i-blog.csdnimg.cn/blog_migrate/2f17f722c964da58b616f433e4600cb5.png#pic_center)
2)、客户端的缓存控制
其实不止服务器可以发Cache-Control头,浏览器也可以发Cache-Control,也就是说请求-应答的双方都可以用这个字段进行缓存控制,互相协商缓存的使用策略
Ctrl+F5的强制刷新其实是发了一个Cache-Control: no-cache,含义和max-age=0基本一样,就看后台的服务器怎么理解,通常两者的效果是相同的
Cache-Control头部在请求中的值:
- max-age:告诉服务器,客户端不会接受Age超出max-age秒的缓存
- max-stale:告诉服务器,即使缓存不再新鲜,但陈旧秒数没有超出max-stale时,客户端仍打算使⽤。若max-stale后没有值,则表示⽆论过期多久客户端都可使⽤
- max-fresh:告诉服务器,如果缓存距离过期还有⾄少min-fresh秒才可使⽤
- no-cache:告诉服务器,不能直接使⽤已有缓存作为响应返回,除⾮带着缓存条件到上游服务端得到304验证返回码才可使⽤现有缓存
- no-store:告诉各代理服务器不要对该请求的响应缓存(实际有不少不遵守该规定的代理服务器)
- no-transform:告诉代理服务器不要修改消息包体的内容
- only-if-cached:告诉服务器仅能返回缓存的响应,否则若没有缓存则返回504错误码
2、Expires
Expires是以前用来控制缓存的http头,Cache-Control是新版的API,现在首选Cache-Control
- 如果在Cache-Control响应头设置了max-age或者s-max-age指令,那么Expires头会被忽略
- 响应头设置方式: Expires: Wed, 21 Oct 2015 07:28:00 GMT
- Expires响应头包含日期/时间, 即在此时候之后,响应过期
因为过期标准的时间用的是本地时间,所以不靠谱,所以要使用Cache-Control代替Expires
一般会把Cache-Control和Expires都设置上,因为Expires是HTTP 1.0定义的字段,而Cache-Control是HTTP 1.1的字段,万一客户端只支持HTTP 1.0,那么Cache-Control有可能就会不工作,所以一般为了兼容会都写上
3、Last-Modified与Etag
HTTP协议定义了一系列If开头的条件请求字段用来检查验证资源是否过期
条件请求最常用的是if-Modified-Since和If-None-Match这两个。需要第一次的响应报文预先提供Last-modified和ETag,然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的
如果资源没有变,服务器就回应一个304 Not Modified,表示缓存依然有效,浏览器就可以更新一下有效期,然后使用缓存即可
1)、Last-Modify/If-Modify-Since
浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间
当浏览器再次请求该资源时,发送的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存
如果命中缓存,则返回http304,并且不会返回资源内容,并且不会返回Last-Modify。由于对比的服务端时间,所以客户端与服务端时间差距不会导致问题。但是有时候通过最后修改时间来判断资源是否修改还是不太准确(资源变化了最后修改时间也可以一致)。于是出现了ETag/If-None-Match
2)、Etag/If-None-Match
若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据http头信息中的Last-Modify/If-Modify-Since或Etag/If-None-Match来判断是否命中协商缓存。如果命中,则http返回码为304,浏览器从缓存中加载资源
Etag返回的是一个校验码(entity tag)。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化,ETag值的变更则说明资源状态已经被修改。服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存
ETag是HTTP1.1中才加入的一个属性,用来帮助服务器控制Web端的缓存验证。它的原理是这样的,当浏览器请求服务器的某项资源A时, 服务器根据A算出一个哈希值(3f80f-1b6-3e1cb03b)并通过 ETag 返回给浏览器,浏览器把3f80f-1b6-3e1cb03b和A同时缓存在本地,当下次再次向服务器请求A时,会通过类似If-None-Match: “3f80f-1b6-3e1cb03b”的请求头把ETag发送给服务器,服务器再次计算A的哈希值并和浏览器返回的值做比较,如果发现A发生了变化就把A返回给浏览器(200),如果发现A没有变化就给浏览器返回一个304未修改
ETag还有强和弱之分,强ETag要求资源在字节级别必须完全相符,弱ETag在值前有个W/
标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(例如HTML里的标签顺序调整,或者多了几个空格)
为什么有了Last-modified还需要ETag呢?
- Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
- 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存
- 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304
参考:
https://blog.csdn.net/w57685321/article/details/92797551
《20 | 生鲜速递:HTTP的缓存控制》:https://time.geekbang.org/column/article/106804