众所周知,在 Web 开发中,缓存很重要、颇有用。但同时其也很复杂。html
本文将从如下 5 个方面全面地介绍下缓存相关的内容。node
浏览器对于所请求资源的缓存处理有一套完整的机制,主要包含如下三个策略:存储策略、过时策略、协商策略。git
其中,存储策略发生在收到请求响应后,用于决定是否缓存相应资源;过时策略发生在请求前,用于判断缓存是否过时;协商策略发生在请求中,用于判断缓存资源是否更新。github
浏览器在应用缓存策略时,具体的判断流程以下: web
上图中的缓存判断流程是浏览器在应用缓存时完整的判断流程。可是在浏览器中访问资源的方式不一样也会致使判断流程的不一样。判断流程会根据不一样方式跳过一些流程。算法
浏览器下访问资源的方式主要有如下 7 种:segmentfault
使用这 7 种方式访问资源时,应用缓存的策略会有一些不一样。以下图所示。经过上述 7 种方式访问资源,会从不一样的缓存应用判断步骤开始。此处不作验证,相信你们看了后面的内容,可以自行验证的。 浏览器
须要注意的是,Chrome 中在当前地址栏,不改变内容,直接回车,等同于刷新当前页,而在 Firefox 下与其余在地址栏回车同样。这一点比较特殊,须要适当区分下。缓存
本文配有测试脚本,代码在github上。下文会按照测试脚本进行述说,使用说明见下载连接。验证上述内容,能够执行
node cache-ETag+max-age.js
,会同时开启ETag
和max-age
,而后触发相应的动做,经过 Network 面板和 node 日志便可验证,此处篇幅有限先不赘述。安全
此外,这里提一个概念,webkit 资源分为主资源和派生资源。主资源是地址栏输入的 URL 请求返回的资源,派生资源是主资源中所引用的 JS、CSS、图片等资源。
在 Chrome 下刷新时,只有主资源的缓存应用方式如上图所示,派生资源的缓存应用方式与新标签打开相似,会判断缓存是否过时。强缓存生效时的区别在于新标签打开为from disk cache
,而当前页刷新派生资源是from memory cache
。
而在 Firefox 下,当前页面刷新,全部资源都会如上图所示。下文也会利用 Chrome 的这一特色在当前页刷新,派生资源会使用缓存进行测试。否则每次都须要打开新标签较为繁琐。
HTTP 中与缓存有关的字段主要有如下 10 个,以下表所示。为明确表示其功能及用法,下表中分别区分了存储策略、过时策略、协商策略、请求头、响应头。
注:乄表示半对,Last-Modified
之因此是半对,是由于有可能会触发启发式缓存,也会缓存文件。具体见下文。
缓存又分为强缓存和弱缓存(又称为协商缓存)。其中强缓存包括
Expires
和Cache-Control
,主要是在过时策略生效时应用的缓存。弱缓存包括Last-Modified
和ETag
,是在协商策略后应用的缓存。强弱缓存之间的主要区别在于获取资源时是否会发送请求。
如上所述,Expires
指定缓存的过时时间,为绝对时间,即某一时刻。参考本地时间进行比对,在指定时刻后过时。RFC 2616建议最大值不要超过 1 年。
Expire
头字段是响应头字段,格式以下:Expires: Sat Oct 20 2018 00:00:00 GMT+0800 (CST)
。
能够尝试如下步骤进行验证:
node cache-Expires.js
,该脚本会给请求的资源设定
Expires
,值为:"2018-10-20 00:00:00"。
http://localhost:1030/
,开启 Network Tab,查看 avatar.jpg 图片,Expires 值以下所示。
(from memory cache)
。此时修改本地时间,将时间修改成“2018-10-15 00:00:00”,再刷新,会发现缓存仍然有效。
Cache-Control
用于指定资源的缓存机制,能够同时在请求头和响应头中设定,涉及上述三个策略中的两个策略:存储策略、过时策略。
Cache-Control
的语法以下:Cache-Control: cache-directive[,cache-directive]
。cache-directive
为缓存指令,大小写不敏感,共有 12 个与 HTTP 缓存标准相关,以下表所示。其中请求指令 7 种,响应指令 9 种。Cache-Control
能够设置多个缓存指令,以逗号,
分隔。
如上,cache-directive 指令大小写不敏感,因此在设置 Cache-Control 时,指令能够不区分大小写。不过建议统一使用小写。验证以下:
node cache-directive-case-insensitive.js
,会服务端会将
max-age
写成大写,以下
Cache-Control: MAX-AGE=86400
。
max-age 在请求头中的主要应用为max-age=0
表示不使用缓存。Chrome 和 Firefox 浏览器下的刷新操做(Command+ R / F5)均是在请求头上添加了max-age=0
指令,表示不使用强缓存,但容许协商缓存(在介绍了协商缓存的Last-Modified
和ETag
以后,能够自行验证下这一点)。
刷新时Cache-Control
为max-age=0
验证以下:
http://localhost:1030/avatar.jpg
,开启 Network
此外,经验证,Chrome 和 Firefox 均对max-age
>0 的状况支持很差。
Modify Headers
插件(Chrome 和 Firefox 下均有相似插件)给请求添加
max-age=7200
。
node cache-max-age.js
,访问
http://localhost:1030
,先强刷保证资源更新。
avatar.jpg
,刷新,会发现,资源访问仍然走的是缓存。若是按照规范的定义应该是不生效。
Cache-Control 中的max-age
指令用于指定缓存过时的相对时间。资源达到指定时间后过时。该功能与 Expires 相似。但其优先级高于 Expires,若是同时设置 max-age 和 Expires,max-age 生效,忽略 Expires。验证以下:
node cache-max-age+Expires.js
,会同时设置
Cache-Control: max-age=86400
/
Expires: Mon Oct 20 2018 00:00:00 GMT+0800 (CST)
,以下所示。
相反,能够再试一下,max-age 的有效时间大于 Expires 的状况,会发现依然是 max-age 生效。
还有一点须要注意的是,no-cache 并非指不缓存文件,no-store 才是指不缓存文件。no-cache 仅仅是代表跳过强缓存,强制进入协商策略。
http1.0 字段, 一般设置为Pragma:no-cache
, 做用与Cache-Control:no-cache
相同。当在浏览器进行强刷(Comand + Shift + R / Ctrl + F5)或在 NetWork 面板内勾选禁用缓存(Disable Caches)时,会自动带上Pragma:no-cache
和Cache-Control:no-cache
,而且不会带上协商策略中所涉及的信息(下面介绍的If-Modified-Since
/If-None-Match
)。这是不会使用任何缓存,从新获取资源。以下图所示。
Last-Modified
用于标记请求资源的最后一次修改时间。语法格式为:Last-Modified: <day-name>,<day> <month> <year> <hour>:<minute>:<second> GMT
,即 GMT(格林尼治标准时间)。可用 new Date().toGMTString()获取当前 GMT 时间。因为 Last-Modified 只能精确到秒,所以不适合在一秒内屡次改变的资源。
若是 Expires,Cache-Control: max-age,或 Cache-Control:s-maxage 都没有在响应头中出现,而且设置了Last-Modified
时,那么浏览器默认会采用一个启发式的算法,即启发式缓存。一般会取响应头的 Date_value - Last-Modified_value 值的 10%做为缓存时间。验证以下:
node cache-Last-Modified.js
,服务器会获取资源的最后修改时间,设置为
Last-Modified
的值。访问
localhost:1030
,查看
avatar.jpg
,以下图所示:
返回的资源带有Last-Modified
标识时,再次请求该资源,浏览器会自动带上If-Modified-Since
,值为返回的Last-Modified
值。请求到达服务器后,服务器进行判断,若是从上次更新后没有再更新,则返回 304。若是更新了则从新返回。验证以下:
node cache-Last-Modified.js
,服务器会获取资源的最后修改时间,设置为
Last-Modified
的值。以下图所示,而且注意看一下资源的大小。
If-Modified-Since
。若是服务器判断资源未改变,则返回 304,此外因为服务器返回 304,资源会从缓存获取,因此资源大小也减小了,以下所示。
index.html
文件的内容,再次刷新。会发现返回变成 200,html 内容更新了,而且返回了新的
Last-Modified
的值,资源大小也相应地改变了。
304 请求也能够触发存储策略,如文章开头的流程判断图所示,可自行验证,返回时添加相应 header 便可。
注意,If-Modified-Since
只能用于 GET、HEAD 请求。
If-Unmodified-Since
表示资源未修改则正常执行更新,不然返回 412(Precondition Failed)状态码的响应。主要有以下两种场景。
If-Range
字段同时使用时,能够用来保证新的片断请求来自一个未修改的文档。
ETag 是请求资源在服务器的惟一标识,浏览器能够根据 ETag 值缓存数据。在再次请求时经过If-None-Match
携带上次的 ETag 值,若是值不变,则返回 304,若是改变你则返回新的内容。
须要注意的是,ETag 和 If-None-Match 的值均为双引号包裹的。
验证步骤与Last-Modified
类似。执行node cache-ETag.js
便可。此处再也不详述。
If-Match
判断逻辑逻辑与If-None-Match
相反。
最后,ETag
的优先级高于Last-Modified
。当ETag
和Last-Modified
,ETag
优先级更高,但不会忽略Last-Modified
,须要服务端实现。验证以下,其中服务端判断优先级:
node cache-ETag+Last-Modified.js
。服务端会在资源的响应头中,同时设置
ETag
和
Last-Modified
。以下图:
index.html
请求时 304。查看 node 日志,会看到
ETag生效
。以下:
好了,经过长长的第二部分,咱们简单介绍了一下 HTTP Cache 的基础知识。下面我再汇总一下各种缓存之间的优缺点吧。以下表所示:
从上面各种缓存的优缺点能够看出,每一种缓存都不是完美的。因此建议像下面这样作:
Cache-Control
和
ETag
来控制 HTML 中所使用的静态资源的缓存。通常是将
Cache-Control
的
max-age
设成一个比较大的值,而后用
ETag
进行验证。
还有两个本文没有介绍的内容,可是不建议你们使用:
看了这么多内容,是时候来看当作果了。那么一块儿看下下面的问题吧。
若是首次访问localhost:1030
时,页面中 avatar.png 响应头信息以下:
HTTP/1.1 200 OK Cache-Control: no-cache Content-Type: image/png Last-Modified: Tue, 16 Oct 2018 11:42:28 GMT Accept-Ranges: bytes Date: Tue, 16 Oct 2018 15:57:21 GMT 复制代码复制代码
问题 1:请问当刷新该页面后,avatar.png 如何二次加载?
问题 2:若是将上述信息中的Cache-Control
设置为 private,那么结果又会如何呢?
你们先回忆下上面的内容,思考一下。
试题来源:完全弄懂 Http 缓存机制 - 基于缓存策略三要素分解法。在此致谢。
好了公布答案。
问题 1:会带着If-Modified-Since
和服务端进行验证。未改变返回 304,改变返回 200。
问题 2:Cache-Control
设置为 private,这时候会触发启发式缓存,则再次刷新时,avatar.png 命中强缓存,从缓存中换取。
好了,文章到此结束,但愿能对你们有帮助。
感谢《深刻浅出 Vue.js》做者刘博文对本文提出的宝贵建议。