(之前觉得HTTP缓存是个简单的事,项目中遇到后才发觉关于缓存实践有挺深的学问)html
这里的缓存指的是http标准中定义的缓存技术(如Cache-Control),主要由服务端设置和处理(固然还要由客户端旳浏览器配合)而不须要前端开发者参与。当作了恰当的参数设置后客户端发起请求过程当中缓存会由浏览器和服务端间自动进行处理完成,而不用用户参与。前端
当前HTML5 API中有LocalStorage、SessionStorage技术也被用于“缓存”,然而这类本质上是种相似于DB的本地存储,由开发者进行控制。这种不是咱们这里所要讨论的缓存。web
整个 Web 系统架构在 HTTP 协议 之上, 利用 HTTP 的缓存机制不只能够极大地减小服务器负载, 更重要的是加速页面的载入以及减小用户的流量消耗。 HTTP缓存机制也早已普遍地被服务器厂商(如 Tomcat、Apache、Virgo)和浏览器厂商(如Tomcat、Apache、Virgo)实现。此外,一些服务器端框架(如 Django、Express.js)也实现了 HTTP 缓存机制。chrome
浏览器和服务器之间使用的缓存策略能够分为强缓存、协商缓存两种,一般一块儿搭配使用。浏览器
强缓存:用户发送的请求,直接从用户客户端缓存读取,不发送到服务端,无与服务端交互缓存
协商缓存:用户发送的请求,发送到服务端,由服务端根据参数判断是否让客户端从客户端缓存读取。协商缓存没法减小请求开销,但可减小返回的正文大小服务器
相同点:最终都是从客户端缓存读取而不用服务端发送请求的数据回来;架构
不一样点:客户端的请求是否发送到服务端,便是否与服务端交互负载均衡
适用性:强缓存适用于不多变化的资源,能够把过时时间设长;协商缓存适用于常常变化的资源。框架
与强缓存和协商缓存相关的HTTP Header参数以下:(注:HTTP标准中Header名首字母大写)
Date:用于记录这次缓存时服务器的时间
Cache-Control、Expires、Pragma:用于强缓存的判断
Last-Modified、 If-Modified-Since、Etag、 If-None-Match:用于协商缓存判断,以实现有条件的HTTP请求
后面用到时详细介绍。
总体过程以下,若(b)是N则是使用强缓存、若(c)为Y则是使用协商缓存。
总体过程可理解为:
(a)浏览器判断是否有缓存
浏览器会在系统某个位置专门存放缓存信息。经过检查该位置是否有对应请求的缓存信息来判断是否有缓存(对于Chrome 66以前的版本也可在chrome://cache 查看请求及对应的缓存信息)。
缓存信息包括请求的响应头及对应的缓存内容。一个缓存示例以下:
(b)判断缓存是否过时
客户端检查到本地有缓存的话会判断缓存是否过时。
缓存信息中包含被缓存的请求的响应头。里面包含Date、Cache-Control、Expires、Pragma字段(可能不是每一个都有)用于判断缓存是否过时。下面介绍各字段的做用再说明判断方法。
各字段旳做用:
Date:指明这次缓存的时间(服务器时间)
Expires:指明缓存过时的绝对时间(服务器时间),如 Thu, 28 Sep 2017 06:38:37GMT 。http 1.0的标准。存在的问题:客户端服务端时间不一致可能致使缓存效果不符合指望。
Cache-Control:指明缓存过时策略,可当作是Expires的补充,使用相对时间。http 1.1的标准。属性设置:
Cache-Control的设置规则:
Pragma:只有 Pragma: no-cach 一种用法,与Cache-Control:no-cache的做用同样。出现缘由:http 1.0没有实现no cache的功能,所以用Pagama使no cache功能应用到1.0。
判断缓存是否过时的规则:
若Cache-Control中有max-age或s-maxage则用它们加上date做为过时绝对时间,不然直接用expires指定的时间做为过时绝对时间。将绝对时间与当前时间比较是否过时。若未过时则直接使用缓存(此时就是前面说的强缓存)。
(c)向服务器询问是否使用缓存
若客户端判断缓存已过时,则向服务端发送请求。服务端根据Last-Modified/If-Modified-Since、Etag/If-None-Match字段(也可能不是每一个都有)判断是否让客户端用缓存。
下面一样先介绍各字段做用再说明判断方法。
各字段做用:(这几个字段一般用于有条件的HTTP请求)
Last-Modified:代表请求的服务端资源上次的修改时间(服务器时间)
If-Modified-Since:客户端保留的资源上次的修改时间
Etag:服务端根据资源内容生成的一段标识(不惟一,一般为文件的md5或者hash值或版本号等,只要保证写入和验证时的方法一致便可)
If-None-Match:客户端保留的上次获取到的的Etag
判断规则(这部分一般服务器实现或服务端代码本身实现):
浏览器向服务端发送请求时,若上一次的缓存中有Last-Modified或Etag字段则在request header中加入If-Modified-Since(对应Last-Modified)或If-None-Match字段(对应If-None-Match),以询问服务端资源是否被修改过。服务端判断资源是否修改(对于If-Modified-Since服务端看自该时间后资源是否修改、对于If-None-Match服务端比较资源目前的Etag是否与所收到的同样):若未修改则服务端返回304,浏览器使用缓存;不然浏览器再次请求资源,状态码为200、资源为服务器最新资源。Etag处理流程示意图:
一般状况下,若同时发送If-None-Match、If-Modified-Since字段,服务器只要比较Etag的内容便可(即Etag的优先级高于另者),固然具体处理方式,看服务器的约定规则。
注:
使用ETag能够解决Last-modified存在的一些问题:
分布式系统中尽可能不用Etag,由于每台机器生成的Etag都同样。(啥意思???)
分布式系统里多台机器间资源的Last-Modified必须一致,以避免负载均衡不一样致使对比失败
至此,结合上述参数的整个请求处理过程以下:
Chrome浏览器从本地缓存中取资源时,发起的请求中会有"from memory cache" 或 "from disk cache" 标识(最先只有from cache,从某个版本起改成此二者),示例:
Chrome from memory cache与from disk cache的区别:
Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to call handlerBehaviorChanged() after registering or unregistering an event listener.
用户操做 | Cache-Control/Expires | Last-Modified/Etag |
地址栏回车 | 有效 | 有效 |
页面连接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进、后退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl + F5刷新 | 无效 | 无效 |
// 设置缓存。强缓存适合不频繁变动的文件,协商缓存用于变化频繁的文件。咱们的业务场景中资源不多变化,故以强缓存为主、协商缓存几乎触发不到。 { // 如下设置强缓存 int cacheTimeSecond = 5 * 3600;// 强缓存时间 Date curDate = new Date(); Date newLastModifyDate = PersistentStorageUtilFactory.getPersistStorageUtil() .getObjectMetaData(bucketName, objKey).getLastModified(); response.setHeader("Date", curDate.toGMTString()); response.setHeader("Cache-Control", "private, max-age=" + cacheTimeSecond);// second。时间太长的话可能致使前端缓存的资源与服务端资源不一致。max-age会覆盖expires response.setDateHeader("expries", curDate.getTime() + cacheTimeSecond * 1000); // 如下设置协商缓存 String newEtagStr = String.valueOf(newLastModifyDate.hashCode()); SimpleDateFormat sdf = new SimpleDateFormat(); String oldModifiedTimeStr = request.getHeader("If-Modified-Since"); String oldEtagStr = request.getHeader("If-None-Match"); boolean isModifiedTimeNotChanged = null != oldModifiedTimeStr && sdf.parse(oldModifiedTimeStr).equals(newLastModifyDate); boolean isContentNotChanged = null != oldEtagStr && oldEtagStr.equals(newEtagStr); if (isModifiedTimeNotChanged || isContentNotChanged) {// 服务器资源没有更新。返回304 response.setStatus(HttpStatus.SC_NOT_MODIFIED); return; } else { response.setHeader("Last-Modified", newLastModifyDate.toGMTString()); response.setHeader("Etag", newEtagStr); } }
https://blog.csdn.net/u014590757/article/details/80140654
http://www.alloyteam.com/2016/03/discussion-on-web-caching/
https://excaliburhan.com/post/things-you-should-know-about-browser-cache.html