HTTP缓存:当web请求抵达缓存时,如果本地已经有缓存并且缓存还没有过期,就直接使用缓存中的资源,而无需再向服务器发送请求。
缓存好处:
- 减少了不必要数据的传输,节省了网费;
- 减轻了服务器端的压力,提高了网站的性能;
- 加快了客户端加载页面的速度,提高了用户体验。
浏览器端的缓存规则
对于浏览器端的缓存来说,这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。他们分别从新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。
新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的:
- 含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内;
- 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度;
满足以上两个情况的一种,浏览器会直接从缓存中获取副本并渲染。
校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。
浏览器缓存的控制
使用HTML Meta 标签
Web开发者可以在HTML页面的<head>
节点中加入<meta>
标签,代码如下:
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。使用上很简单,但只有部分浏览器可以支持,而且所有缓存代理服务器都不支持,因为代理不解析HTML内容本身。
使用缓存有关的HTTP消息报头
在HTTP请求和响应的消息报头中,常见的与缓存有关的消息报头有:
Expires与Cache-Control
首先说明一点:Cache-Control与Expires的作用一致,都是指明当前缓存文件的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。
对于上图中的响应头来说:
Date是当前时间,Expires规定了缓存失效时间(日期),而Cache-Control的max-age规定了缓存有效时间(距离失效的秒数),理论上这两个值计算出的有效时间应该是相同的。Expires属于HTTP1.0,而Cache-Control属于HTTP1.1。此外,Expires有一个缺点:它的过期时间是服务器的时间,假如我的客户端时间和服务器时间相差很大,那误差就很大。比如服务器返回的是2016年7月16号过期,我的电脑时间被我修改了,快了一天为2016年7月17号,那客户端缓存就过期了。所以它被Cache-Control:max-age=秒替代了。
注意:**规定如果max-age和Expires同时存在,Cache-Control优先级高于Expires。**Cache-Control的参数可以设置很多值,譬如(参考浏览器缓存机制):
Cache-Control头
**值可以是:**public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age
各个消息中的指令含义是:
- public:表示响应可被任何缓存区缓存
- private: 表示对于单个用户的整个或部分部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效
- no-cache: 表示请求或响应消息不能缓存
- no-store:用于防止重要的信息被无意的发布,在请求消息中发送将使得请求和响应消息都不使用缓存
- max-age:表示从当前请求开始,允许获取的响应被重用的最长时间(单位为秒) - 例如:max-age=5000表示响应可以再缓存和重用 5000 秒。
- min-fresh:指示客户机可以接收响应时间小于当前时间加上指定时间的响应
- max-stale:指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期间指定之内的响应消息
Last-Modified和ETag与Cache-Control和Expires
配置Last-Modified/ETag的情况下,浏览器再次访问统一URI(统一资源标识符)的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过,那就最新的文件发给浏览器;
Cache-Control/Expires则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时,Cache-Control/Expires的优先级要高于Last-Modified/ETag。即当本地副本根据Cache-Control/Expires发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(Last-Modified)或实体标识(Etag)了。
注意:一般情况下,需要Cache-Control/Expires配合Last-Modified/ETag一起使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时Last-Modified/ETag将能够很好利用304,从而减少响应开销(这里只是避免了服务器响应数据,也就是说如果响应304。服务不再返回数据,浏览器直接使用缓存文件,但是浏览器端依旧会发送请求)。
Last-Modified与If-Modified-Since
Last-Modified:标识响应资源的最后修改时间,web服务器在响应请求时,告诉浏览器资源的最后修改时间。
Last-Modified/If-Modified-Since就是缓存过期后,检查服务端文件是否更新的第一种方式,要配合Cache-Control使用。比如响应头信息:
按下ctrl+r刷新,因为ctrl+r会默认跳过max-age和Expires的检验直接去向服务器发送请求,看下图:
请求头中包含了If-Modified-Since项,而它的值和上次请求响应头中的Last-Modified一致,我们发现这个日期是2013年,也就是说这个文件自从2013年的那个日期后就没有再被修改过了。
核心思想:当缓存资源过期后(也就是 Cache-Control:max-age=0),假设该资源具有Last-Modified声明,则再次向服务器请求时带上头 If-Modified-Since,表示请求时间。服务器收到请求后发现有头If-Modified-Since,则与被请求资源的最后修改时间进行比对。若Last-Modified的时间较新,说明最后修改时间较新,说明资源又被改动过,则响应整的资源重新从服务器读取,而不是读取缓存,返回HTTP200;若If-Modified-Since的时间比Last-Modified新或者两者相等,说明服务器的内容没有更新,直接读取缓存即可,返回HTTP304,告知浏览器继续使用所保存的缓存资源,同时更新响应头last-Modified的值(以备下次对比)。
缺点:
- 有些文档可能会被周期性地重写,但实际包含的数据常常是一样的。尽管内容没有变化,但是修改日期会发生变化。
- 有些文档可能被修改了,但所做修改并不重要,不需要让世界范围内的缓存都重装数据(比如对拼音或注释的修改)。
- 有些服务器无法准确地判定其页面的最后修改日期。
- 有些服务器提供的文档会在亚秒间隙发生变化,对这些服务器来说,以秒为粒度的修改日期就不够用了。
这个问题就需要下面说到的ETag来解决了。
ETag与If-None-Match
ETag/If-None-Match是第二种检测服务端文件是否更新的方式,也要配合Cache-Control使用。
Etag(实体标签): 服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。所以我们不用管它是怎么生成的,我们只需要清楚:每个文件都有一个唯一的标识,只要这个文件发生了改变,这个标识就会发生变化。
实际上ETag并不是文件的版本号,而是一串可以代表该文件唯一的字符串(Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的)。
当缓存资源过期之后,如果浏览器发现服务器端返回的头部信息具有Etage声明,就在请求中发送If-None-Match选项,值即为上次请求后响应头的ETag值,该值在服务端和服务端代表该文件唯一的字符串对比(如果服务端该文件改变了,该值就会变),如果相同,则响应HTTP304,客户端直接读取缓存,如果不相同,则响应HTTP200,返回最新的数据资源,更新ETag值。
结合上图来说:当浏览器缓存过期之后,客户端就会重新向服务器发送请求,请求头中带If-None-Match项,该字符串值会在服务端进行匹配,很显然,并没有什么变化(对比响应头的ETag值),于是响应HTTP304,直接读取缓存。
细心的你或许发现了:该请求中也有If-Modified-Since项,如果两者同时存在,If-None-Match优先,忽略If-Modified-Since。或许你会问为什么它优先?两者功能相似甚至相同,为什么要同时存在?HTTP1.1中ETag的出现主要是为了解决几个Last-Modified比较难解决的问题:
- Last-Modified标注的最后修改只能精确到秒,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
- 如果某些文件会被定期生成,但有时内容并没有任何变化(仅仅改变了时间),但Last-Modified却改变了,导致文件没法使用缓存
- 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
用户操作行为与缓存
通过上表我们可以看到,当用户在按F5进行刷新的时候,会忽略Expires/Cache-Control的设置,会再次向服务器发送请求,而Last-Modified/Etag还是有效的,服务器会根据情况判断返回304还是200;而当用户使用Ctrl+F5进行强制刷新的时候,所有的缓存机制都将失效,重新从服务器获取资源。
哪些请求不能被缓存?
无法被浏览器缓存的请求:
HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache,或Cache-Control:max-age=0等告诉浏览器不用缓存的请求
需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
- 经过HTTPS安全加密的请求(有人也经过测试发现,ie其实在头部加入Cache-Control:max-age信息,firefox在头部加入Cache-Control:Public之后,能够对HTTPS的资源进行缓存,参考《HTTPS的七个误解》)
- POST请求无法被缓存
- HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求无法被缓存
小结
特别说明:图片来源
上图对于浏览器缓存机制描述的很详细,这里我对其做了一下语言描述。
个人理解,如有错误,请指正
首先声明一点:对于第一次请求,无论是静态文件还是其他文件,都需要从服务器返回,并不存在缓存一说。等第一次请求完,浏览器就有缓存了,然后整个的加载过程就完全不一样了。这里重点说明有缓存的情况。
当客户端向服务器发送一个请求时,首先检查是否存在缓存,有缓存文件则判断缓存文件是否过期,如果缓存文件没有过期,则直接从缓存中读取文件。
如果缓存文件过期,则查看响应头中是否存在Etag(优先级高于Last-Modified),如果不存在,则继续查看响应头中是否存在Last-Modified,如果也不存在,则直接向服务器发送新的请求,等待请求响应,缓存协商,呈现页面。
如果缓存文件过期,响应头中存在ETag(实体标签),向服务器发送带If-None-Match的请求,其值即为上次请求后响应头的ETag值,该值在服务端和服务端代表该文件唯一的字符串对比(如果服务器端该文件更新了,该值就会变),如果相同,则响应HTTP304,客户端直接读取缓存文件,如果不相同,则响应HTTP200,服务器返回最新的文件,同时更新ETag值。
如果缓存文件过期,响应头中不存在Etag,但是存在Last-Modified,则向服务器发送带If-Modified-Since的请求,将If-Modified-Since的日期和服务端该文件的最后修改日期对比,如果两个日期相同,则响应HTTP304,客户端直接读取缓存文件;如果不相同则表示文件发生变化更新,响应HTTP200,从服务器返回最新的文件数据,同时更新响应头last-Modified的值(以备下次对比)。
说明:之前也知道浏览器的缓存机制,但是总是迷迷糊糊的。今天特地查了很多资料,并和小伙伴们一起讨论了这个问题,算是就加深了理解吧。特此整理了一下,这里特别感谢参考博文的作者,学习还是要多思考,多查资料,多总结,继续加油!!