HTTP缓存总结

最近由于工作需要,对http缓存做了较为全面的了解。发现目前网络上已经充斥着各种各样相关主题的文章,但是这些文章要么记录比较浅显,要么不是很方便以后阅读查看,于是决定自己写一篇进行整理和备忘。

概述

为获取网页内容需要浏览器与服务器之间进行多次通信,这些通信会延迟浏览器获得和处理内容的时间,并对服务器造成巨大的开销。通过缓存可以降低服务器的压力并载提高网页的整体加载速度,这也是前端页面性能优化很重要的一环。浏览器自带了 HTTP 缓存实现,页面的缓存状态是由header决定的,通过请求和响应header来指示浏览器是否缓存和缓存时间。header的参数有四种:Expires,Cache-Control,Last-modified,ETag。
大体上说,这些缓存策略分为两种:强缓存(本地缓存)和协商缓存 ,其中Expires和Cache-Control归为强缓存,Last-modified和ETag归为协商缓存。

强缓存

Expires:

Expires是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间(服务器端的具体时间点),由服务器返回,用GMT格式的字符串表示,
如: expires:Tue, 17 Apr 2018 09:05:55 GMT。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

Cache-Control:

由于Expires是较老的强缓存管理header,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题,比如随意修改下客户端时间,就能影响缓存命中的结果。所以在http1.1的时候,提出了一个新的header,就是Cache-Control,这是一个相对时间,在配置缓存的时候,以秒为单位,用数值表示,如:Cache-Control:max-age=315360000。cache-control常用的设置值如下:

  • max-age:缓存的相对时间,单位为秒
  • no-cache:不使用本地缓存,但仍然会缓存服务器返回的数据
  • no-store:不允许缓存响应,每次请求都必须完整获取
  • public:可以被所有的用户缓存。
  • private:只能被终端用户的(客户端)浏览器缓存,不允许CDN等中继缓存服务器对其缓存。
    Cache-Control与Expires可以在服务端配置同时启用,同时启用的时候Cache-Control优先级高。

协商缓存

Last-Modify和If-Modify-Since:

浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify标识服务器端该资源的最后修改时间,例如 Last-Modify: Thu,31 Dec 2017 23:59:59 GMT
当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。向服务器询问Last-Modified时间点之后资源是否被修改过,如果没有修改,则返回304,使用缓存,并且不会返回Last-Modify。如果修改过,则再次去服务器请求资源,返回码和首次请求相同为200,资源为服务器最新资源。

Etag和If-None-Match

Etag/If-None-Match返回的是一个校验码。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。服务器根据浏览器上送的If-None-Match值来判断是否命中缓存。
与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化。
Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

缓存处理流程

当有缓存时,浏览器处理流程如下:

  1. 浏览器请求某一资源时,会先根据获取缓存的header信息来决定如何加载资源
  2. 强缓存判断:根据header中的Cache-Control和Expires来判断强缓存是否过期,若未过期直接读取本地缓存(200(from cache)不发起http请求),否则进入步骤3判断
  3. 协商缓存判断:浏览器会向服务器端发送请求,这个请求会携带第一次请求返回的有关缓存的header字段信息,
    1. 当存在Etag时。客户端会首先通过If-None-Match头将先前服务器端发送过来的Etag发送给服务器,服务器会对比这个Etag是否与服务器的相同,若相同,就将If-None-Match的值设为false,返回状态304,客户端继续使用本地缓存,不解析服务器端发回来的数据,若不相同就将If-None-Match的值设为true,返回状态为200,客户端重新获取服务器端返回的数据;
    2. 当不存在Etag时。客户端还会通过If-Modified-Since头将先前服务器端发过来的最后修改时间戳发送给服务器,服务器端通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回最新的内容,如果是最新的,则返回304,客户端继续使用本地缓存。

缓存更新与版本号

如果你已告诉访问者将某个 CSS 样式表缓存长达 24 小时 (max-age=86400),但设计妹子刚刚又提交了一个您希望所有用户都能使用的更新。你该如何通知拥有现在“已过时”的 CSS 缓存副本的所有访问者更新其缓存?在不更改资源网址的情况下,您做不到!
浏览器缓存响应后,缓存的版本将一直使用到过期(由 max-age 或 expires 决定),或一直使用到由于某种其他原因从缓存中删除,例如用户清除了浏览器缓存。因此,构建网页时,不同的用户可能最终使用的是文件的不同版本;刚获取了资源的用户将使用新版本的响应,而缓存了早期(但仍有效)副本的用户将使用旧版本的响应。
所以,如何才能鱼和熊掌兼得:客户端缓存和快速更新?您可以在资源内容发生变化时更改它的网址,强制用户下载新响应。通常情况下,可以通过在文件名中嵌入文件版本号来实现 - 例如 style.x234dff.css。

对于使用缓存及版本号文件如何部署优化问题,可以参考这篇文章

如果文章有用,请随意打赏