之前在做项目的过程中,有一件很痛苦的事情就是每次前端修改文件后(静态资源),必须得清空下浏览器缓存才能生效,所以就看了下浏览器缓存相关的知识点。
http 协议中有这样几个 header:Last-Modified、ETag、Cache-Control、Expires 、If-None-Match 、If-Modified-Since等。访问一个网页可以在地址栏回车,可以F5刷新,Ctrl+F5强制刷新、浏览器的刷新按钮等多种方式。让人想不到的是:不同的访问方式对于缓存的使用有很大区别。
这里就不介绍这些header是干什么用的了,网上资料很多,请自行查找。这里总结下几个容易忽略的点:
- Last-Modified 、ETag 是设置在response里面的,两者可以一起用,也可以只用其中一个;
- If-Modified-Since 、If-None-Match 是设置在request中的,由浏览器从资源的上次响应中提取Last-Modified 、ETag值(如果有,没有的话,服务器提取到的值是-1和null),然后发给服务器;
- Last-Modified只能精确到秒级。当在服务端设置响应的Last-Modified值时,即使设置的值精确到了毫秒并返回给浏览器。但是浏览器下次请求时携带的If-Modified-Since值也会丢失毫秒值,因此如果用Last-Modified来校验文件是否变化,一般会在其基础上加上1000L来进行比较;
- 不管Cache-Control是什么值,浏览器都会缓存资源。很多人以为Cache-Control设置了 no-cache 之类的值后,浏览器就不会缓存资源了。其实不然,不管该值为什么,浏览器都会缓存下来。
不同的访问方式对于缓存的处理以及请求的构造是不同的:
Ctrl+F5 强制刷新
浏览器请求时,不管有没有缓存,浏览器都会重新发起一次http请求,并且不会携带If-None-Match 、If-Modified-Since值。服务器收到这次请求后,因为没有相应的If-None-Match 、If-Modified-Since值,无法验证资源的状态,所以会重新返回内容,状态设为200。浏览器收到响应后,再重新缓存。
F5 刷新、刷新按钮
这种请求方式下,与上一个类似:不管有没有缓存,浏览器都会重新发起一次http请求。不同的是,如果资源之前有缓存过,就会设置相应的If-None-Match 、If-Modified-Since值(如果有)到 request中,服务器根据这些值与文件的状态来确定返回的状态码:200还是304。如果文件没有修改过,就返回304,并且不传输任何内容;如果文件修改过,就返回200,并将新的内容返回。不管返回什么状态,缓存文件都会持续更新,比如返回304的时候,Cache-Control 的值又设置了一个新的值,那么原有的缓存文件也会更新此值。
地址栏回车、前进、后退、新窗口、新标签页等
这种方式下,如果Cache-Control或者Expires 设置的值还有效,那么浏览器不会发起http请求,而是直接从缓存中取对应的内容(此时服务器打断点也进不去可以验证);如果Cache-Control或者Expires无效了,那么浏览器就会重新发起http请求并携带If-Modified-Since 、If-None-Match头(如果有),这时就与上面的处理过程类似了。
从上面可以看出,Last-Modified、ETag、Cache-Control、Expires、If-None-Match 、If-Modified-Since 这些header是没有任何冲突的,使用场景也划分的很清晰。Last-Modified、ETag设置在响应里面,用于浏览器在 request 的时候设置这些值到 If-Modified-Since、If-None-Match。Cache-Control、Expires 的值是在用地址栏回车等访问方式的情况下,浏览器用来判断是否发起http请求的依据,并不是用来决定是否进行缓存的依据。如果有效,就不发起请求;如果缓存过期或者no-cache等无效状态,则发起请求。
前面说过不管Cache-Control是什么值,浏览器都会缓存资源,怎样验证?假设第一次请求某个资源时,response 设置 Cache-Control 为 no-cache,并设置了ETag 或 Last-Modified;在资源不修改的情况下,第二次继续访问,通过携带的 If-Modified-Since 、If-None-Match ,服务器会返回304,而且可以不返回任何内容。如果第一次不缓存的话,这里又不返回任何内容,那么我们肯定无法显示资源,而实际上,第二次不返回任何内容,也是可以正常显示资源的,所以可以验证不管Cache-Control是什么值,浏览器都会缓存资源。
上面的这些,我是写了一个Filter 来进行验证的,Filter 代码如下:
|
|
web.xml 配置如下:
|
|
需要注意的是,上面说的这些行为是针对浏览器发起的请求,不针对非浏览器发起的请求(比如 ajax 请求等)。