HTTP缓存是个大公司面试几乎必考的问题,写篇随笔说一下HTTP缓存。html
在HTTP报文中,与缓存相关的信息都存在首部里,简单说一下首部。web
HTTP首部字段向请求报文和相应报文中添加了一些附加信息。本质上来讲,它们只是一些键值对的列表。好比,下面的首部行会向Content-Length首部字段赋值19:面试
Content-Length: 19
HTTP规范定义了几中首部字段。应用程序也能够随意发明本身所用的首部。HTTP首部能够分为如下几类:算法
通用首部浏览器
既能够出如今请求报文中,也能够出如今响应报文中。缓存
请求首部服务器
提供更多有关请求的信息。网络
响应首部分布式
提供更多有关响应的信息。ui
实体首部
描述主体的长度和内容,或资源自己。
扩展首部
规范中没有定义的新首部。
想了解更多有关HTTP首部或报文的信息,我的推荐《HTTP权威指南》。
通用首部字段
请求首部字段
响应首部字段
实体首部字段
除了一些微小的细节,Web缓存的工做原理基本很简单,对一条HTTP GET报文的基本缓存处理过程包括7个步骤。
缓存的处理步骤如图:
经过特殊的HTTP Cache-Control首部和Expires首部,HTTP让原始服务器向每一个文档加了一个过时时间,这些首部说明了多长时间内可将这些内容视为新鲜的。
在文档过时以前,缓存能够以任意频率使用这些副本,而无需与服务器进行联系,除非客户端请求中包含有阻止提供已缓存或未验证资源的首部。一旦已缓存的文档过时,缓存就必须与服务器进行核对,询问文档是否被修改过,若是被修改过,就要获取一份新鲜的并带有新的过时日期的副本。
可是缓存文档过时并不意味着它与服务器上的文档有实际的区别,只是觉得到了要进行核对的时间了。这种状况叫服务器再验证,说明缓存须要询问服务器文档是否发生了变化。
若是再验证显示内容发生了变化,缓存会获取一份新的文档副本,并将其存储在旧文档的位置上,而后将文档发送给客户端。
若是再验证显示内容没有发生变化,缓存只要获取新的首部,包括一个新的过时日期,并对缓存中的首部进行更新就行了。
HTTP定义了几个首部用来实现缓存是否新鲜的验证,像开篇咱们说到的If-Modified-Since和If-None-Match、Last-Modified等都属于这样的首部。
咱们根据是否须要向服务器发起请求将缓存分红两类,不须要向服务器发起请求的缓存叫强缓存,也就是上面所说的文档没过时时候用到的缓存。须要向服务器发起请求的缓存叫协商缓存,也就是上面服务器再验证用到的缓存。
下面咱们详细介绍强缓存和协商缓存。
上面咱们说能够经过Cache-Control首部和Expires首部来标明文档的过时时间。若是没有过时的话,天然就是从缓存里取文档了:
顾名思义,这里的memory cache内存了,disk cache就是磁盘的缓存了,再往下说就到了webkit的缓存机制了。
为何要先说Expires呢?由于相比于Cache-Control,Expires出现的较早,是HTTP1.0的东西,而Cache-Control是HTTP1.1的东西。
Expires的值对应一个GMT,也就是格林尼治时间,好比“Mon, 22 Jan 2019 11:12:01 GMT”来告诉浏览器资源缓存过时时间,若是还没过该时间点则不发请求。
在客户端咱们一样可使用meta标签来知会IE(也仅有IE能识别)页面(一样也只对页面有效,对页面上的资源无效)缓存时间:
<meta http-equiv="expires" content="mon, 18 apr 2016 14:30:00 GMT">
若是但愿在IE下页面不走缓存,但愿每次刷新页面都能发新请求,那么能够把“content”里的值写为“-1”或“0”。可是是该方式仅仅做为知会IE缓存时间的标记,你并不能在请求或响应报文中找到Expires字段。
那么若是Pragma和Expires一块儿出现的话,Pragma的优先级是高的。
注意:响应报文中Expires所定义的缓存时间是相对服务器上的时间而言的,若是客户端上的时间跟服务器上的时间不一致,特别是若是你修改了本身电脑的系统时间,那缓存时间可能就没什么意义了。
既然咱们已经说了Expires是HTTP1.0的遗留物,那咱们也要介绍下Pragma。
当该字段值为“no-cache”的时候,会通知客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。
Pragma属于通用首部字段,在客户端上使用时,常规要求咱们往html上加上这段meta元标签:
<meta http-equiv="Pragma" content="no-cache">
它告诉浏览器每次请求页面时都不要读缓存,都得往服务器发一次请求才行。
可是这种禁用缓存的形式做用不是那么太大:
仅有IE才能识别这段meta标签含义,其它主流浏览器仅能识别“Cache-Control: no-store”的meta标签。
在IE中识别到该meta标签含义,并不必定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求,可是仅限页面,页面上的资源则不受影响。
因此这种在客户端定义Pragma并无多少做用。
可是在响应报文中加上这个字段就不同了,浏览器在收到服务器的Pragma字段后会对资源进行标记,禁用其缓存行为,进然后续每次刷新页面均能从新发出请求而不走缓存。
针对上述的“Expires时间是相对服务器而言,没法保证和客户端时间统一”的问题,HTTP1.1新增了 Cache-Control 来定义缓存过时时间,若报文中同时出现了 Pragma、Expires 和 Cache-Control,会以 Cache-Control 为准。
Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。在RFC中规范了 Cache-Control 的格式为:
"Cache-Control" ":" cache-directive"
做为请求首部时,cache-directive的可选值有:
做为响应首部时,cache-directive的可选值有:
咱们依旧能够在HTML页面加上meta标签来给请求报头加上 Cache-Control字段,而且能够有多个值:
Cache-Control: max-age=3600, must-revalidate
它意味着该资源是从原服务器上取得的,且其新鲜度的有效时间为一小时,在后续一小时内,用户从新访问该资源则无须发送请求。
固然这种组合的方式也会有些限制,好比 no-cache 就不能和 max-age、min-fresh、max-stale 一块儿搭配使用。
组合的形式还能作一些浏览器行为不一致的兼容处理。例如在IE咱们可使用 no-cache 来防止点击“后退”按钮时页面资源从缓存加载,但在 Firefox 中,须要使用 no-store 才能防止历史回退时浏览器不从缓存中去读取数据,故咱们在响应报头加上以下组合值便可作兼容处理:
Cache-Control: no-cache, no-store
顾名思义,客户端经过与服务器进行协商是否使用缓存。前面咱们已经说过了HTTP提供了实现缓存文件是否新鲜的验证、提高缓存的复用率的几个首部,就来讲说这些首部。其实它们都是HTTP1.1新增的。
服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一块儿返回给客户端。
客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去作检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码便可。
至于传递标记起来的最终修改时间的请求报文首部字段一共有两个:
⑴ If-Modified-Since: Last-Modified-value
示例:
If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT
该请求首部告诉服务器若是客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头便可。
当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。
⑵ If-Unmodified-Since: Last-Modified-value
告诉服务器,若Last-Modified没有匹配上,即资源在服务端的最后更新时间改变了,则应当返回412(Precondition Failed) 状态码给客户端。
当遇到下面状况时,If-Unmodified-Since 字段会被忽略:
Last-Modified值相等,即资源在服务端没有新的修改;
服务端需返回2XX和412以外的状态码;
传来的指定日期不合法
Last-Modified 说好却也不是特别好,由于若是在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会由于Last-Modified时间匹配不上而返回了整个实体给客户端。
为了解决上述Last-Modified可能存在的不许确的问题,Http1.1还推出了 ETag 实体首部字段。
服务器会经过某种算法,给资源计算得出一个惟一标志符(好比md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 惟一标识符”一块儿返回给客户端。
客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只须要比较客户端传来的ETag跟本身服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。
若是服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源以及新的Etag发给客户端;若是ETag是一致的,则直接返回304知会客户端直接使用本地缓存便可。
那么客户端是如何把标记在资源上的 ETag 传去给服务器的呢?请求报文中有两个首部字段能够带上 ETag 值:
⑴ If-None-Match: ETag-value
示例为
If-None-Match: "56fcccc8-1699"
告诉服务端若是 ETag 没匹配上须要重发资源数据,不然直接回送304 和响应报头便可。
当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。
⑵ If-Match: ETag-value
告诉服务器若是没有匹配到ETag,或者收到了“*”值而当前并无该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。不然服务器直接忽略该字段。
If-Match 的一个应用场景是,客户端走PUT方法向服务端请求上传/更替资源,这时候能够经过 If-Match 传递资源的ETag。
须要注意的是,若是资源是走分布式服务器(好比CDN)存储的状况,须要这些服务器上计算ETag惟一值的算法保持一致,才不会致使明明同一个文件,在服务器A和服务器B上生成的ETag却不同。
若是 Last-Modified 和 ETag 同时被使用,则要求它们的验证都必须经过才会返回304,若其中某个验证没经过,则服务器会按常规返回资源实体及200状态码。
若是面试的时候能说出这些,就表明了你对HTTP缓存理解的很不错了,若是是一百分的话也应该能够拿到八十分了。