以下是对HTTP中控制客户端缓存的几种方式以及优先级的梳理
浏览器在请求已经访问过的URL的时候, 会判断是否使用缓存, 判断是否使用缓存主要通过判断缓存是否在有效期内, 通过两个字段来判断:
当缓存过期后, 浏览器不会直接去服务器上拿缓存, 而是判断缓存是否有更新, 能否继续使用, 判断的方法有两种:
参考:
彻底弄懂HTTP缓存机制及原理
知乎专栏-浏览器是如何控制缓存的
本文内容大多参考 《图解HTTP》一书
所以讲缓存为什么要先扯代理服务器?别急,让我们看一下一个请求的简单示意图。
我们看到客户端(用户)发送了一个请求并不是直接发给源服务器的而是经过了代理服务器,然后经由代理服务器再发送给源服务器,响应也同样遵循这个顺序。
那么代理服务器在这中间担任了什么角色?
缓存是指代理服务器或客户端本地磁盘内保存的资源副本。利用缓存可减少对源服务器的访问,因此也就节省了通信流量和通信时间。
缓存服务器是代理服务器的一种,并归类在缓存代理类型中。换句话说,当代理转发从服务器返回的响应时,代理服务器将会保存一份资源的副本。
缓存服务器的优势在于利用缓存可避免多次从源服务器转发资源。因此客户端可就近从缓存服务器上获取资源,而源服务器也不必多次处理相同的请求了。
即便缓存服务器和客户端内有缓存,也不能每次都给我返回缓存吧,如果是这样,源服务器更新了我也不知道,因为我每次都是看缓存的资源。
为了解决这个问题,针对缓存设计了时效性的概念:
即使存在缓存,也会因为客户端的要求、缓存的有效期等因素,向源服务器确认资源的有效性。若判断缓存失效,缓存服务器将会再次从源服务器上获取“新”资源。
缓存不仅可以存在于缓存服务器内,还可以存在客户端浏览器中。以Internet Explorer 程序为例,把客户端缓存称为临时网络文件(Temporary Internet File)。
浏览器缓存如果有效,就不必再向服务器请求相同的资源了,可以直接从本地磁盘内读取。
另外,和缓存服务器相同的一点是,当判定缓存过期后,会向源服务器确认资源的有效性。若判断浏览器缓存失效,浏览器会再次请求新资源。
Pragma 是HTTP/1.1 之前版本的历史遗留字段,仅作为与HTTP/1.0的向后兼容而定义。
规范定义的形式唯一,如下所示。
Pragma: no-cache
该首部字段属于通用首部字段,但只用在客户端发送的请求中。客户端会要求所有的中间服务器不返回缓存的资源。
通过指定首部字段Cache-Control 的指令,就能操作缓存的工作机制。
可用的指令按请求和响应分类如下所示:
public指令
Cache-Control: public
当指定使用public 指令时,则明确表明其他用户也可利用缓存。
private指令
no-store指令
Cache-Control: no-store
当使用no-store 指令时,暗示请求(和对应的响应)或响应中包含机密信息。
因此,该指令规定缓存不能在本地存储请求或响应的任一部分。
ps:从字面意思上很容易把no-cache误解成为不缓存,但事实上no-cache代表不缓存过期的资源,缓存会向源服务器进行有效期确认后处理资源,也许称为do-not-serve-from-cache-without-revalidation更合适。no-store 才是真正地不进行缓存,请读者注意区别理解。
s-maxage指令
Cache-Control: s-maxage=604800 //(单位:秒)
s-maxage 指令的功能和max-age 指令的功能相同, 它们的不同点是s-maxage 指令只适用于供多位用户使用的公共缓存服务器(这里指代理服务器)。也就是说,对于向同一用户重复返回响应的服务器来说,这个指令没有任何作用。
另外,当使用s-maxage 指令后,则直接忽略对Expires 首部字段及max-age 指令的处理。
max-age指令
cache-extension token
Cache-Control: private, community="UCI"
通过 cache-extension 标记(token),可以扩展Cache-Control 首部字段内的指令。
如上例,Cache-Control 首部字段本身没有community 这个指令。借助extension tokens 实现了该指令的添加。如果缓存服务器不能理community 这个新指令,就会直接忽略。因此,extension tokens 仅对能理解它的缓存服务器来说是有意义的。
If-Unmodified-Since: Thu, 03 Jul 2012 00:00:00 GMT
首部字段If-Unmodified-Since 和首部字段If-Modified-Since 的作用相反。它的作用的是告知服务器,指定的请求资源只有在字段值内指定的日期时间之后,未发生更新的情况下,才能处理请求。如果在指定日期时间后发生了更新,则以状态码412 Precondition Failed 作为响应返回。
ps:Last-Modified 存在一定问题,如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。
首部字段If-None-Match 属于附带条件之一。它和首部字段If-Match 作用相反。用于指定If-None-Match 字段值的实体标记(ETag)值与请求资源的ETag 不一致时,它就告知服务器处理该请求。
在GET 或HEAD 方法中使用首部字段If-None-Match 可获取最新的资源。因此,这与使用首部字段If-Modified-Since 时有些类似。
不与服务器确认,而是直接使用浏览器缓存的内容。其中响应内容和之前的响应内容一模一样,例如其中的Date时间是上一次响应的时间。
F5的作用和直接在URI输入栏中输入然后回车是不一样的,F5会让浏览器无论如何都发一个HTTP Request给Server,即使先前的响应中有Expires头部。
Ctrl+F5要的是彻底的从Server拿一份新的资源过来,所以不光要发送HTTP request给Server,而且这个请求里面连If-Modified-Since/If-None-Match都没有,这样就逼着Server不能返回304,而是把整个资源原原本本地返回一份,这样,Ctrl+F5引发的传输时间变长了,自然网页Refresh的也慢一些。
Cache-Control 是 HTTP1.1 才有的,不适用于 HTTP1.0,而 Expires 既适用于 HTTP1.0,也适用于 HTTP1.1,所以说在大多数情况下同时发送这两个头会是一个更好的选择,当客户端两种头都能解析的时候,会优先使用 Cache-Control。
二者都是通过某个标识值来请求资源, 如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed)状态码,内容为空,这样就节省了传输数据量。当资源变化后则返回新资源。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
其中Last-Modified使用文件最后修改作为文件标识值,它无法处理文件一秒内多次修改的情况,而且只要文件修改了哪怕文件实质内容没有修改,也会重新返回资源内容;ETag作为“被请求变量的实体值”,其完全可以解决Last-Modified头部的问题,但是其计算过程需要耗费服务器资源。
Expires和Cache-Control都有一个问题就是服务端的修改,如果还在缓存时效里,那么客户端是不会去请求服务端资源的(非刷新),这就存在一个资源版本不符的问题,而强制刷新一定会发起HTTP请求并返回资源内容,无论该内容在这段时间内是否修改过;而Last-Modified和Etag每次请求资源都会发起请求,哪怕是很久都不会有修改的资源,都至少有一次请求响应的消耗。
对于所有可缓存资源,指定一个Expires或Cache-Control max-age以及一个Last-Modified或ETag至关重要。同时使用前者和后者可以很好的相互适应。
前者不需要每次都发起一次请求来校验资源时效性,后者保证当资源未出现修改的时候不需要重新发送该资源。而在用户的不同刷新页面行为中,二者的结合也能很好的利用HTTP缓存控制特性,无论是在地址栏输入URI然后输入回车进行访问,还是点击刷新按钮,浏览器都能充分利用缓存内容,避免进行不必要的请求与数据传输。
做法很简单,就是把可能会更新的资源以版本形式发布,常用的方法是在文件名或参数带上一串md5或时间标记符:
可以看到上面的例子中有不同的做法,有的在URI后面加上了md5参数,有的将md5值作为文件名的一部分,有的将资源放在特性版本的目录中。
那么在文件没有变动的时候,浏览器不用发起请求直接可以使用缓存文件;而在文件有变化的时候,由于文件版本号的变更,导致文件名变化,请求的url变了,自然文件就更新了。这样能确保客户端能及时从服务器收取到新修改的文件。通过这样的处理,增长了静态资源,特别是图片资源的缓存时间,避免该资源很快过期,客户端频繁向服务端发起资源请求,服务器再返回304响应的情况(有Last-Modified/Etag)。
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)