[服务端与网络]浏览器缓存

274 阅读8分钟
本文仅作为交流学习,主要为juejiner我的总结复习之用,还望大佬们轻喷

前言

前端性能优化中很重要的一环就是缓存,浏览器向服务器发起请求,这个请求会经历浏览器发起网络请求,服务器处理,浏览器响应三个步骤。浏览器缓存起到的作用即是节省了一三步骤的时间,比如直接从本地内存或是硬盘中读取缓存不发起请求或者发起请求后端发现数据与前端一致则无需传送数据回来继续从缓存中读取数据。缓存的机制也非常多,本文来看下http缓存机制。

一 http缓存及解析过程

http缓存是基于HTTP协议的浏览器文件级缓存机制。即针对文件的重复请求情况下,浏览器可以根据协议头判断从服务器端请求文件还是从本地读取文件。

以下是浏览器缓存的整个机制流程。主要是针对重复的http请求,在有缓存的情况下判断过程主要分3步如下图

                                            

                                    浏览器缓存判断过程(图片来自参考中链接)     

◆判断expires和Cache-Control,如果未过期,直接读取http缓存文件,不发http请求,否则进入下一步。

◆判断是否含有etag,有则带上if-none-match发送请求,未修改返回304,修改返回200,否则进入下一步。

◆判断是否含有last-modified,有则带上if-modified-since发送请求,无效返回200,有效返回304,否则直接向服务器请求。

如果通过etag和last-modified判断,即使返回304有至少有一次http请求,只不过返回的是304的返回内容,而不是文件内容。所以合理设计实现expires参数可以减少较多的浏览器请求。


                                    浏览器第一次发起请求(图片来自参考中链接)     

上图讲解了浏览器缓存的读取和存入。这是缓存的关键。这时候再来了解下缓存策略。

二 缓存策略

我们根据缓存时是否向浏览器发起请求来分为两大类,强制缓存和协商缓存。

2.1 强制缓存(Expires 和 Cache-Control)

直接从缓存中读取资源,不发送请求。在chrome控制台的Network/size选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

1.Expires

Expires 是 HTTP/1.0 控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间(GMT String),即再次发起该请求时,如果客户端的时间小于 Expires 的值时,直接使用缓存结果。

在原来cookie的过期时间也是用Expires来控制而非现在的max-age。

由于 Expires 的设置是本地时间与服务器时间作对比,则当服务器所在时区与浏览器端不同时会产生判断误差导致缓存误差,这时在http1.1中提出了 Cache-Control 属性。

2. Cache-Control

其值主要有以下几个:

    • max-age=[单位:秒 seconds] — 设置缓存最大的有效时间. 类似于 Expires, 但是这个参数定义的是时间大小(比如:60)而不是确定的时间点.单位是[秒 seconds].
    • s-maxage=[单位:秒 seconds] — 类似于 max-age, 但是它只用于公享缓存 (e.g., proxy) .
    • public — 响应会被缓存,并且在多用户间共享。正常情况, 如果要求 HTTP 认证,响应会自动设置为 private.
    • private — 响应只能够作为私有的缓存(e.g., 在一个浏览器中),不能再用户间共享。
    • no-cache — 响应不会被缓存,而是实时向服务器端请求资源。这一点很有用,这对保证HTTP 认证能够严格地禁止缓存以保证安全性很有用(这是指页面与public结合使用的情况下).既没有牺牲缓存的效率,又能保证安全。
    • no-store — 在任何条件下,响应都不会被缓存,并且不会被写入到客户端的磁盘里,这也是基于安全考虑的某些敏感的响应才会使用这个。
    • must-revalidate — 响应在特定条件下会被重用,以满足接下来的请求,但是它必须到服务器端去验证它是不是仍然是最新的。
    • proxy-revalidate — 类似于 must-revalidate,但不适用于代理缓存.

浏览器端通过头部信息来设置其值,服务器端在程序中设置其值

<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate" />
<meta http-equiv="expires" content="Thu, 01 Jan 1970 00:00:01 GMT" />
<meta http-equiv="expires" content="0" />

需要注意的是当 Expires 与 Cache-Control 同时存在时优先生效 Cache-Control。

这里再提下强制缓存的一些应用场景,在资源文件请求中常常被应用,打开控制台 network看一下,在Size 栏发现了不少的 (memory cache) 与 (disk cache)。 在time上发现内存缓存是0ms, 而硬盘缓存是 1ms, 这里可以涉及到一个缓存的三级原理, 简单一句话就是: 从内存到硬盘再到网络请求三级。 这样的顺序当然是因为内存最快,硬盘次之,重新请求最慢了。

我们可以将一些不常更新的资源作为长缓存 max-age给一个一年, 而对于一些敏感的业务文件或者更新频繁的资源设置其 max-age 为 0, 则立刻过期。

2.2 协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。协商缓存生效成功返回状态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。

XMLHttpRequest.setRequestHeader("If-Modified-Since","0"); // 将协商失败
XMLHttpRequest.send(null);


总结

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存。在实际应用场景中,根据需求来设置不过期来达到减少http请求或减少服务器传输压力最终提升性能的目的。

参考

彻底理解浏览器的缓存机制

HTTP 缓存机制一二三

深入理解浏览器的缓存机制

前端性能优化----yahoo前端性能团队总结的35条黄金定律