1. 浏览器缓存
浏览器缓存的知识是前端工程师必须要掌握的,因为这些知识直接影响到你的页面的用户体验,影响到你的页面的加载策略。接下来将要详细的讲述浏览器缓存的概念和原理,缓存的知识除了和浏览器有关,还涉及到HTTP协议。
1.1 概念
浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。浏览器缓存主要有两类:缓存协商和强缓存。
1.2 请求流程图
上图是浏览器第一次请求时发生过流程,浏览器显然不存在任何缓存数据,浏览器跳过校验缓存这一步,直接进入下一个流程,向网络发起请求,也就是上图中的[ 向web服务器发起请求 ]。
这个过程包含很多内容,将在后续的内容叙述。
从上面的请求流程图可以知道,浏览器缓存包含两种类型,即强缓存(也叫本地缓存)和协商缓存。
强缓存
强缓存是与(cache-control和expires信息)有关的缓存。
cache-control
Cache-Control 是最重要的规则。这个字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令。这些指令指定用于阻止缓存对请求或响应造成不利干扰的行为。这些指令通常覆盖默认缓存算法。缓存指令是单向的,即请求中存在一个指令并不意味着响应中将存在同一个指令。
cache-control 定义是:Cache-Control = “Cache-Control” “:” cache-directive。
网页的缓存是由HTTP消息头中的“Cache-control”来控制的,常见的取值有private、no-cache、max-age、must-revalidate等,默认为private。
表 1. 常用 cache-directive 值
Cache-directive |
说明 |
public |
所有内容都将被缓存(客户端和代理服务器都可缓存) |
private |
内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存) |
no-cache |
必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。 |
no-store |
所有内容都不会被缓存到缓存或 Internet 临时文件中 |
must-revalidation/proxy-revalidation |
如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证 |
max-age=xxx (xxx is numeric) |
缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高 |
表 2 表明在不同的情形下,浏览器是将请求重新发送到服务器还是使用缓存的内容。
表 2. 对 cache-directive 值的浏览器响应
Cache-directive |
打开一个新的浏览器窗口 |
在原窗口中单击 Enter 按钮 |
刷新 |
单击 Back 按钮 |
public |
浏览器呈现来自缓存的页面 |
浏览器呈现来自缓存的页面 |
浏览器重新发送请求到服务器 |
浏览器呈现来自缓存的页面 |
private |
浏览器重新发送请求到服务器 |
第一次,浏览器重新发送请求到服务器;此后,浏览器呈现来自缓存的页面 |
浏览器重新发送请求到服务器 |
浏览器呈现来自缓存的页面 |
no-cache/no-store |
浏览器重新发送请求到服务器 |
浏览器重新发送请求到服务器 |
浏览器重新发送请求到服务器 |
浏览器重新发送请求到服务器 |
must-revalidation/proxy-revalidation |
浏览器重新发送请求到服务器 |
第一次,浏览器重新发送请求到服务器;此后,浏览器呈现来自缓存的页面 |
浏览器重新发送请求到服务器 |
浏览器呈现来自缓存的页面 |
max-age=xxx (xxx is numeric) |
在 xxx 秒后,浏览器重新发送请求到服务器 |
在 xxx 秒后,浏览器重新发送请求到服务器 |
浏览器重新发送请求到服务器 |
在 xxx 秒后,浏览器重新发送请求到服务器 |
Cache-Control是关于浏览器缓存的最重要的设置,因为它覆盖其他设置,比如 Expires 和 Last-Modified。另外,由于浏览器的行为基本相同,这个属性是处理跨浏览器缓存问题的最有效的方法。
Expires
Expires 头部字段提供一个日期和时间,响应在该日期和时间后被认为失效。失效的缓存条目通常不会被缓存(无论是代理缓存还是用户代理缓存)返回,除非首先通过原始服务器(或者拥有该实体的最新副本的中介缓存)验证。(注意:cache-control max-age 和 s-maxage 将覆盖 Expires 头部。)
Expires 字段接收以下格式的值:“Expires: Sun, 08 Nov 2009 03:37:26 GMT”。如果查看内容时的日期在给定的日期之前,则认为该内容没有失效并从缓存中提取出来。反之,则认为该内容失效,缓存将采取一些措施。
不同操作下不同浏览器的响应。具体相关内容参见百度百科词条。
协商缓存
协商缓存是与Last-Modified和Etag相关的缓存。
Last-Modified
在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是客户端请求的资源,同时有一个Last-Modified的属性标记此文件在服务器端最后被修改的时间。
Last-Modified格式类似这样:
Last-Modified : Fri , 12 May 2006 18:53:33 GMT
客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头,询问该时间之后文件是否有被修改过:
If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
如果服务器端的资源没有变化,则自动返回 HTTP 304(Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
Etag的概念
HTTP协议规格说明定义ETag为“被请求变量的实体值”。另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以是一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:ETag:"50b1c1d4f775c61:df3"客户端的查询更新格式是这样的:If-None-Match : W / "50b1c1d4f775c61:df3"如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。测试Etag主要在断点下载时比较有用。
客户端请求一个页面(A)。 服务器返回页面A,并在给A加上一个ETag。 客户端展现该页面,并将页面连同ETag一起缓存。 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。
Etag的优势
1、有些URL是多语言的网页,相同的URL会返回不同的东东。还有不同的Session有不同的Cookie也就有不同的内容。这种情况下如果过 Proxy,Proxy就无法区分导致串门,只能简单的取消cache功能。Etag解决了这个问题,因为它能区分相同URL不同的对象。
2、老的HTTP标准里有个Last-Modified+If-Modified-Since表明URL对象是否改变。Etag也具有这种功能,因为对象改变也造成Etag改变,并且它的控制更加准确。Etag有两种用法 If-Match/If-None-Match,就是如果服务器的对象和客户端的对象ID(不)匹配才执行。这里的If-Match/If-None- Match都能一次提交多个Etag。If-Match可以在Etag未改变时断线重传。If-None-Match可以刷新对象(在有新的Etag时返回)。
3、Etag中有种Weak Tag,值为 W/"xxxxx"。他声明Tag是弱匹配的,只能做模糊匹配,在差异达到一定阈值时才起作用。
4、Etag对于cache CGI页面很有用。特别是论坛,论坛有办法为每个帖子页面生成唯一的Etag,在帖子未改变时,查看话题属性比较Etag就能避免刷新帖子,减少CGI操作和网络传输。比如论坛中看帖就返回Etag,减少论坛负担。
5、Etag在不同URL之间没有可比性,也就是不同URL相同Etag没有特别意义。
Etag的原理
请求流程
Etag由服务器端生成,客户端通过If-Match或者说If-None-Match这个条件判断请求来验证资源是否修改。常见的是使用If-None-Match。请求一个文件的流程可能如下:
====第一次请求===
1.客户端发起 HTTP GET 请求一个文件;
2.服务器处理请求,返回文件内容和一堆Header,当然包括Etag(例如"2e681a-6-5d044840")(假设服务器支持Etag生成和已经开启了Etag)。状态码200。
====第二次请求===
1.客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个If-None-Match头,这个头的内容就是第一次请求时服务器返回的Etag:2e681a-6-5d044840
2.服务器判断发送过来的Etag和计算出来的Etag匹配,因此If-None-Match为False,不返回200,返回304,客户端继续使用本地缓存;
流程很简单,问题是,如果服务器又设置了Cache-Control:max-age和Expires呢,怎么办?
答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.(不要陷入到底使用谁的问题怪圈)
Etag的作用
Etag 主要为了解决 Last-Modified 无法解决的一些问题。
1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET。
2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)。
3、某些服务器不能精确的得到文件的最后修改时间;
为此,HTTP/1.1引入了 Etag(Entity Tags)。Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说"2e681a-6-5d044840"这么一串看起来很神秘的编码。但是HTTP/1.1标准并没有规定Etag的内容是什么或者说要怎么实现,唯一规定的是Etag需要放在""内。
3.3 结束
关于缓存的内容到此告一段落,对于强缓存和协商缓存,不同的浏览器和不同的用户操作行为,都有产生不同的请求操作,对于各个版本的浏览器都需要进行详细的兼容性分析。