缓存过程分析
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:
sequenceDiagram
浏览器->>浏览器缓存: 第一次发起http请求
浏览器缓存-->>浏览器: 没有该请求的缓存结果和缓存标识
浏览器-)服务器: 发起http请求
服务器--)浏览器: 返回请求结果和缓存规则
浏览器->>浏览器缓存: 将该请求结果和缓存标识存入浏览器缓存中
请求过程
- 1、浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 2、浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存 :
-
1)浏览器在加载资源时,先根据这个资源的一些http header判断它是否命中强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。
-
2)当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;
-
3)强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发请求到服务器,协商缓存会发请求到服务器。
-
4)当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。
强缓存的原理
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:
(1)不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致),如下图:
sequenceDiagram
浏览器->>浏览器缓存: 发起http请求
浏览器缓存-->>浏览器: 没有该请求的缓存结果和缓存标识
浏览器-)服务器: 发起http请求
(2)存在该缓存结果和缓存标识,但是结果已经失效,强制缓存失效,则使用协商缓存(暂不分析),如下图
sequenceDiagram
浏览器->>浏览器缓存: 发起http请求
浏览器缓存-->>浏览器: 该请求的缓存失效,只返回缓存标识
浏览器-)服务器: 携带该资源的缓存标识,发起HTTP请求
(3)存在该缓存结果和缓存标识,且该结果没有还没有失效,强制缓存生效,直接返回该结果,如下图:
sequenceDiagram
浏览器->>浏览器缓存: 发起http请求
浏览器缓存-->>浏览器: 该请求的缓存存在,且未失效,直接返回结果
那么强制缓存的缓存规则是什么?
当浏览器向服务器发送请求的时候,服务器会将缓存规则放入HTTP响应的报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Conctrol的优先级比Expires高。
Expires
Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求的结果缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。
到了HTTP/1.1,Expires已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效,那么强制缓存存在的意义就毫无意义。
Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
- (1)public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- (2)private:所有内容只有客户端可以缓存,Cache-Control的默认取值
- (3)no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- (4)no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
- (5)max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
Expires
Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求的结果缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。
Expires是HTTP/1.0的字段,但是现在浏览器的默认使用的是HTTP/1.1,那么在HTTP/1.1中网页缓存还是否由Expires控制?
到了HTTP/1.1,Expires已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效,那么强制缓存存在的意义就毫无意义。
Cache-Control
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
- (1)public:所有内容都将被缓存(客户端和代理服务器都可缓存)
- (2)private:所有内容只有客户端可以缓存,Cache-Control的默认取值
- (3)no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- (4)no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
- (5)max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
由上面的例子我们可以知道:
(1)HTTP响应报文中expires的时间值,是一个绝对值
(2)HTTP响应报文中Cache-Control为max-age=600,是相对值
由于Cache-Control的优先级比expires,那么直接根据Cache-Control的值进行缓存,意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。
📌注:在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。
避免强缓存的方法
- 如果缓存问题出现在ajax请求中,最有效的解决办法就是ajax的请求地址追加随机数;
- 还有一种情况就是动态设置iframe的src时,有可能也会因为缓存问题,导致看不到最新的效果,这时候在要设置的src后面添加随机数也能解决问题;
- 如果你用的是grunt和gulp这种前端工具开发,通过它们的插件比如grunt-contrib-connect来启动一个静态服务器,则完全不用担心开发阶段的资源更新问题,因为在这个静态服务器下的所有资源返回的respone header中,cache-control始终被设置为不缓存:
设置强缓存的方法
1)通过代码的方式,在web服务器返回的响应中添加Expires和Cache-Control Header;
2)通过配置web服务器的方式,让web服务器在响应资源的时候统一添加Expires和Cache-Control Header。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
- 协商缓存生效,返回304,如下
sequenceDiagram
浏览器->>浏览器缓存: 发起http请求
浏览器缓存-->>浏览器: 该请求的缓存失效,只返回缓存标识
浏览器-)服务器: 携带该资源的缓存标识,发起HTTP请求
服务器--)浏览器: 304,该资源无更新
浏览器->>浏览器缓存: 获取该请求的缓存结果
浏览器缓存-->>浏览器: 返回该请求的缓存结果
2.协商缓存失败,返回200和请求结果,如
sequenceDiagram
浏览器->>浏览器缓存: 发起http请求
浏览器缓存-->>浏览器: 该请求的缓存失效,只返回缓存标识
浏览器-)服务器: 携带该资源的缓存标识,发起HTTP请求
服务器--)浏览器: 该资源更新,重新返回请求结果,200
浏览器->>浏览器缓存: 将该请求结果和缓存标识存入浏览器缓存中
同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。
Last-Modified / If-Modified-Since
-
Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间,如下:
-
If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件,如下。
Etag / If-None-Match
- Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),如下:
- If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200,如下。
📌注:Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。
刷新对于强缓存和协商缓存的影响
- 当ctrl+f5强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存。
- 当f5刷新网页时,跳过强缓存,但是会检查协商缓存。
- 浏览器地址栏中写入URL,回车 浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿。(最快)