HTTP缓存
HTTP 缓存是在 HTTP 请求传输时用到的缓存,主要在服务器代码上设置。
浏览器对静态资源的缓存本质上是 HTTP 协议的缓存策略。其中又可以分为强制缓存和协商缓存。两种缓存策略都会将资源缓存到本地。
具体采用哪种缓存策略,由 HTTP 协议的首部(Headers)信息决定。
强制缓存
强制缓存策略根据过期时间决定使用本地缓存还是请求新资源。
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种:
- 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致),如下图:
- 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存,如下图:
- 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果,如下图:
强制缓存的缓存规则:
当浏览器向服务器发起请求时,服务器会将缓存规则放入 HTTP 响应报文的 HTTP 头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是 Expires 和 Cache-Control ,其中 Cache-Control 优先级比 Expires 高。
Expires(文件过期时间)
Expires
是 HTTP/1.0 控制网页缓存的字段,其值为缓存资源的到期时间,即再次发起该请求时,如果客户端的时间小于 Expires 的值时,客户端将使用本地缓存的文件应答请求,而不会向服务器发出实体请求。
Expires 可以在缓存过期时间内减少客户端的 HTTP 请求,不仅节省了客户端处理的时间和提高了Web应用的执行速度,而且也减少了服务器负载以及客户端网络资源的消耗。
然而,Expires 有一个致命的缺陷,它所指定的时间点时以服务器为准的时间,但是客户端进行过期判断时是将本地的时间与此时间点对比。也就是说,如果客户端时间与服务器存在误差,比如服务器的时间是 2017 年 8 月 23 日 13 点,而客户端的时间是 2017 年 8 月 23 日 15 点,那么通过上述 Expires 控制的缓存资源将会失效,客户端将会发送实体请求获取对应资源。
针对这个问题,HTTP1.1 新增了 Cache-Control 首部信息以便更准确地控制缓存。
Cache-Control
Cache-Control
主要用于控制网页缓存,主要取值为:
- public:表示此响应可以被浏览器以及中间缓存器无限期缓存。浏览器请求服务器时,如果缓存时间没到,中间服务器直接返回给浏览器内容,而不必请求源服务器。
- private:Cache-Control 的默认取值,表示此响应可以被用户浏览器缓存,但是不允许任何中间缓存器对其缓存。浏览器请求服务时,中间服务器都要把浏览器的请求传给服务器。
- no-cache:不管本地副本是否过期,每次访问资源,浏览器都要向服务器询问,如果文件没变化,服务器只告诉浏览器继续使用缓存(304)。
- no-store:真正意义上的禁止缓存,禁止浏览器以及所有中间缓存存储任何版本的返回响应,每次访问资源,浏览器都必须请求服务器,并且,服务器不去检查文件是否变化,而是直接返回完成的资源。
- must-revalidate:本地副本过期前,可以使用本地副本;本地副本一旦过期,必须去源服务器进行有效性校验。
- proxy-revalidate:要求代理服务器针对缓存资源向源服务器进行确认。
- s-maxage:缓存服务器对资源缓存的最大时间。
- max-age:指定从请求的时刻开始计算,此响应的缓存副本有效的最长时间(单位:秒)
强制缓存策略下( Cache-Control 未指定 no-cache 和 no-store )的缓存判断流程
下面来通过例子说明:
由上面的例子可以知道:
- HTTP 响应报文中 expires 的时间值,是一个绝对值
- HTTP 响应报文中 Cache-Control 为 max-age=600 ,是相对值
由于 Cache-Control 的优先级比 expires 高,那么直接根据 Cache-Control 的值进行缓存,意思就是说在 600 秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。
如何在浏览器中判断强制缓存是否生效?
这里我们以博客的请求为例,状态码为灰色的请求则代表了使用了强制缓存,请求对应的 Size 值则代表该缓存存放的位置。
from memory cache:代表使用内存中的缓存。
from disk cache:代表使用的是硬盘中的缓存。
浏览器读取缓存的顺序为 memory -> disk -> 服务器。
在浏览器中,浏览器会对 js 和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取,而 CSS 文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存。
内存缓存(from memory cache)
状态码:200
不访问服务器,直接读缓存,从内存中读取缓存。此时的数据是缓存到内存中的,当 kill 进程后,也就是浏览器关闭以后,数据将不存在。这种方式只能缓存派生资源。
内存缓存具有两个特点,分别是速度快和时间限制。
硬盘缓存(from disk cache)
状态码:200
不访问服务器,直接读缓存,从磁盘中读取缓存,当 kill 进程时,数据还是存在。这种方式也只能缓存派生资源。
硬盘缓存是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行 I/O 操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
- 协商缓存生效,返回304
- 协商缓存失效,返回 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
强制刷新可以使得缓存无效。但是对于强缓存,在未过期时,必须更新资源路径才能发起新的请求(更改了路径相当于是另一个资源了,这也是前端工程化中常用到的技巧)。
ETag 比 Last-Modified 更加严谨,如果资源发生变化, ETag 就会发生变化,就会把最新的资源给客户端返回回去,而 Last-Modified 不识别 秒 单位里的修改,如果资源在 秒 单位里发生了修改,那 Last-Modified 也不会发生改变,这样如果只用了 Last-Modified ,客户端得到的资源就不是最新的,但是设定了 Etag 之后,每次客户端发出请求,服务端都会根据资源重新生成一个 Etag ,对性能有影响。
强制缓存优先于协商缓存,若强制缓存( Expires 和 Cache-Control )生效则直接使用缓存,若不生效则进行协商缓存( Last-Modified/If-Modified-Since 和 ETag/If-None-Match ),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器的缓存中;生效则返回 304 ,继续使用缓存。