http 浏览器缓存

以tomcat为例,浏览器和tomcat之间通过如下几个head来控制是否从服务器端获取资源文件还是之间从浏览器cache中去取

Last-Modified/If-Modified-Since,ETag/If-None-Match

下面通过实验的方式直观的看这几个head是如何工作的,首先创建一个demo项目,然后创建一个html文件和一个js文件用来测试(1.html引用1.js)

下面是第一次访问的截图,可以看到1.html和1.js都返回了200,因为这是第一次访问


http 浏览器缓存
 

然后看下1.js的http的head信息如下图

因为是第一次访问,request head里面没有跟cache有关的信息,但是可以看到response head里面有Last-Modified和ETag


http 浏览器缓存
 

下面在访问的时候,request里面就会携带与Last-Modified/Etag对应的HttpRequestHeader:If-Modified-Since和If-None-Match,如下图


http 浏览器缓存
 

关于If-Modified-Since,tomcat会把这个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中

关于If-None-Match,服务器把这个值和资源的ETag值比较看是否改变,如果变了,则返回200和新的文件内容

其实http协议里面是有专门的协议来控制200(from cache)的,就是Cache-Control: max-age=秒 和 Expires,我们可以通过如下的filter来实现js文件的过期时间为1minute

<filter>
		<filter-name>ExpiresFilter</filter-name>
		<filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
		<init-param>
			<param-name>ExpiresByType image</param-name>
			<param-value>access plus 1 minutes</param-value>
		</init-param>
		<init-param>
			<param-name>ExpiresByType text/css</param-name>
			<param-value>access plus 10 minutes</param-value>
		</init-param>
		<init-param>
			<param-name>ExpiresByType application/javascript</param-name>
			<param-value>access plus 1 minutes</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>ExpiresFilter</filter-name>
		<url-pattern>/*</url-pattern>
		<dispatcher>REQUEST</dispatcher>
	</filter-mapping>

 重启tomcat后,访问1.html,一分钟之内就算update 了 1.js文件,浏览器也会忽略他,直接200(from cache)

如下图,


http 浏览器缓存
 

 

可以看到time是0,

对应的http head如下,


http 浏览器缓存
 

就是max-age为60起的作用

可是在我测试的时候偶尔会发现有200(from cache)这样的status出现(在我没有用ExpireFilter的时候),无论是Firefox还是chrome这种状态说明浏览器没有发起请求,直接用local的cache,甚至我已经修改了后台的js文件,有时候还是会出现200(from cache),这个可能是浏览器的某些算法擅自认为这个文件在服务器端没有被更新而之间用本地的cache

贴一段google来的话,如下

这个结论不是很准确,实际上这个问题答案全部都在https://tools.ietf.org/html/rfc7234

简单说一下

浏览器的缓存机制分为两块,也就是规范中的4.2. Freshness 和 4.3. Validation

Freshness

Cache Control与Expires是一组,他们是用来进行Freshness验证,也就是提供客户端检测文件是否足够新鲜,可以无需向服务端发起Validation请求就能保证并未过期可以直接使用。

所有的from cache的请求实际上都是由于浏览器认为本地的缓存资源足够新鲜,所以无需额外请求而直接使用。

具体的判断方法可以参考协议,实际上就是根据本地的时间和服务器返回头的约定信息进行对比验证。

历史问题

为何有两个参数而不是一个参数则是由于历史原因,在HTTP1.0中定义的是Expires,Expires的值是一个明确的过期时间,而后来使用中发现一旦客户端时间与服务器时间不一致就会引发很多缓存问题。

因此在HTTP 1.1中新添加了Cache Control,实现了更优的文件过期声明,比如max-age配置,是一个timespan,告知客户端这个文件多长时间不会过期而不是直接告知过期时间。

Validation

Last-Modified和ETag则是另一组控制信息,他们用来实现Validation。他们的职责是在本地缓存被浏览器判断可能不够新鲜的时候,会用这两组信息向服务器请求数据,如果服务器内容没有改变,那么约定服务器会返回304 HTTP Code表明这个缓存可以直接使用,无需重新拉取,而一旦服务器内容改变了就会返回200,同时返回新的文件内容。

历史问题

至于为何有两组字段来解决这个问题依然是历史问题,在HTTP 1.0中约定的是Last-Modified,他代表的含义是文件最后一次修改的时间,那么这种约定带来的问题是一旦内容是动态生成的,这个时间在服务器端不一定可以正确的生成,其次是Last-Modified只有秒级的精度,如果在一秒内有一次以上的文件修改,这样的缓存就会造成额外的问题。

因此HTTP1.1中新引入了ETag,他的实现不尽相同,对于动态内容,常规做法是对动态内容做HASH计算,作为ETAG返回,对于静态资源,一般是使用inode+mtime进行计算。

额外的问题

ETag也有他自己的问题,所以经常在使用中会关闭ETag。举例来说,同一个文件在不同物理机上的inode是不同的,这就导致了在分布式的Web系统中,当访问落在不同的物理机上时会返回不同的ETag,进而导致304失效,降级为200请求。解决办法也有从ETag算法中剥离inode,只是使用mtime,但是这样实际上和Last-Modified就一样了。当然你也可以额外的做一些改进,使ETag对静态资源的算法也是通过hash计算的。不过由于一般我们会使用CDN技术,因此集群部署中的ETag问题并不会造成太大的影响,所以折腾的人也不太是很多。

总结

总结一下,ETag的设置并不会影响Freshness验证,Freshness验证会和浏览器策略以及Cache Control与Expires有关。

ps:https://stackoverflow.com/questions/1665082/http-status-code-200-cache-vs-status-code-304

https://developer.yahoo.com/performance/rules.html#expires=

如果有一个2M的js文件需要做到200(from cache)的话,首先可以让他10年不过期,用这个配置access plus 10 years,然后给js命名bundle.0.2.0.js,这样下次build的时候html就应该引入bundle.0.2.1.js这样之前的文件自然就失效了

相关推荐