前端经常会被问HTTP缓存,那么HTTP缓存的发展了解多少呢?为什么会出现各种不同的缓存字段?本篇文章会从缓存的类型开始,逐一说明缓存相关的HTTP字段,相信读完会对以上的问题有所了解。注意,以下讲解只针对HTTP缓存的情况。
缓存分强缓存与协商缓存两种,简单来讲的话,强缓存期间请求不会到达服务器,即在缓存有效期内命中缓存,浏览器会使用本地缓存;协商缓存是在没有命中缓存或强缓存失效的情况下,与服务器进行协商来决定返回的请求资源。大概的流程如下:
看完上图,从整体上我们了解了HTTP请求的流程,那么这里又引出一些问题,如何知道强缓存的存在?依赖什么条件来确定本地是否有效呢?要解答这些问题,就得知道两个HTTP响应头字段
Expires
与Cache-Control
。在HTTP/1.0规范中,响应头字段有一个
Expires
字段,用于表示当前资源过期的时间。该字段描述的是一个绝对时间,由服务器返回,客户端本地只要超过这个时间则缓存失效。示例
Expires: Sun, 24 May 2020 07:41:19 GMT
。Expires
是最初的缓存控制字段,为它开始web应用减少了很多不必要的带宽浪费,可以指定一些不常更新的文件形成缓存。但开发者们很快发现了新的问题,Expires
依赖客户端的时间进行判断,客户端修改了时间会影响缓存的有效性,这在某些情况下会比较致命,比如一个重要文件,理应在今天过期,但由于客户端时间修改成了去年,造成了缓存没有刷新,这就会很尴尬。为了解决这个问题,在HTTP/1.1中加入了Cache-Control
字段来控制缓存,以满足更多的需求,配置如下。需要注意的是,该字段优先级高于Expires
。
-
public
可以被所有用户缓存,这里包括浏览器、代理服务器、CDN等。 -
private
只能被浏览器缓存,不允许被其它如代理服务器缓存。 -
no-store
不缓存 -
no-cache
先缓存本地,但命中缓存后必须与服务器验证资源新鲜度才能使用。这里注意,不要被名字误导成不缓存了。 -
max-age
用相对时间来表示缓存有效期,表示缓存将在XX秒后失效。示例Cache-Control: max-age=315360000
Cache-Control设置
如果在缓存有效期内命中缓存,则使用强缓存,但是,如果命中缓存时缓存失效了怎么办?这个时候就要进入协商缓存流程了。进入协商缓存的条件不单是缓存失效,还包含以下三种情况:
- 第一次请求服务器,返回的响应头中没有
Cache-Control
与Expires
-
Cache-Control:max-age
与Expires
导致的缓存失效 Cache-Control: no-cache
当存在以上三种情况时,第二次请求服务器就会进入协商缓存流程。协商缓存过程也涉及了HTTP状态码的改变,当请求发现服务器存在缓存或且缓存没有更新时,更新缓存时间并返回304 Not Modified
;如果缓存失效,则服务器会把最新资源的完整版返回给浏览器,状态码为200 OK
。看到这里大概就已经明白了协商缓存的过程,但细想一下还有一些没有说清楚的地方,服务器是如何校验缓存的新鲜度的呢?
Last-Modified/If-Modified-Since
当浏览器首次请求服务器资源时,服务器会将请求资源的最新修改时间Last-Modified: Tue, 07 Apr 2020 06:24:10 GMT
通过响应头部返回给浏览器,浏览器在下次发起同一资源请求时会带上该信息,通过在请求头部设置If-Modified-Since: Tue, 07 Apr 2020 06:24:10 GMT
,服务器会比对Last-Modified/If-Modified-Since
如果服务器的资源有更新,则将最新的资源返回给浏览器,并更新响应头中的Last-Modified
值,此时响应状态码为200 OK
;如果服务器资源没有更新(两者时间一致),浏览器直接使用缓存即可,此时响应状态码为304 Not Modified
。
ETag/If-None-Match/If-Match
理论上通过比对资源的最新更新时间即可判断缓存是否有效,但实际操作中还是会遇到问题,比如资源文件在一秒内多次修改、资源经过编辑但没有发生实质内容修改的情况,这些问题表明以Last-Modified
来判断还不够精确,我们需要引入ETag
字段。大体流程上ETag
与Last-Modified
的校验差不多,区别在于ETag
不使用文件修改时间,而是使用文件内容摘要算出来的hash值进行判断,只要资源实质发生修改就会刷新hash值。服务器通过响应头中的ETag
告知浏览器文件缓存信息,那么浏览器是如何把ETag
的信息发送给服务器的呢?有以下两种形式:
-
If-None-Match: ETag value
:通知服务器,如果没有匹配上,则需要重新发送资源;如果匹配上则直接返回304 Not Modified
。一般浏览器使用该字段来回传ETag
值。 -
If-Match: ETag value
:通知服务器,如果没有匹配上ETag,或当前ETag: *
但没有资源实体,则返回412 Precondition Failed
;如果匹配上了,则服务器不做任何处理。
采用分布式服务器(如CDN)时,需要保证各服务器上的ETag算法一致,才不会出现A服务器与B服务器同一文件不同ETag
值的出现。
Pragma
在整理资料的时候还发现了这个字段,该字段是HTTP/1.0
的遗留之物,按辈分讲与Expires
平齐。该字段用来定义是否需要缓存,可选值只有Pragma: no-cache
,这个就可以按照字面意义理解成不进行缓存,即每次都会请求服务器的资源。优先级来讲Pragma > Cache-Control > Expires
,当然,现代规范还是推荐使用Cache-Control
的。