JavaWeb学习篇之----浏览器缓存问题详解
2、静态下Apache、Lighttpd和Nginx中Etag和Expires配置
3、非实时交互动态页面中Etag和Expires处理
标识,等于告诉Client端,你拿到的这个的资源有表示ID:5d8c72a5edda8d6a:3239。当下次需要发Request索要同一个URI的时候,浏览器同时发出一个If-None-Match报头( Http Request Header)此时包头中信息包含上次访问得到的Etag: “5d8c72a5edda8d6a:3239″标识。
expires使用了特定的时间,并且要求服务器和客户端的是中严格同步。
而Cache-Control是用max-age指令指定组件被缓存多久。
对于不支持http1.1的浏览器,还是需要expires来控制。所以最好能指定两个响应头。但HTTP规范规定max-age指令将重写expires头。
第一次访问:
请求:
返回:
第二次访问:
请求:
返回:
这时返回的状态码是304,而在请求中比第一次多了If-Modified-Since头,和原始服务器中的Last-Modified制作比较,所以实现浏览器缓存并判断是否过期。
简单的说,Last-Modified 与If-Modified-Since 都是用于记录页面最后修改时间的 HTTP 头信息,只是 Last-Modified 是由服务器往客户端发送的 HTTP 头,而 If-Modified-Since 则是由客户端往服务器发送的头,可以看到,再次请求本地存在的 cache 页面时,客户端会通过 If-Modified-Since 头将先前服务器端发过来的 Last-Modified 最后修改时间戳发送回去,这是为了让服务器端进行验证,通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回新的内容,如果是最新的,则返回304告诉客户端其本地 cache 的页面是最新的,于是客户端就可以直接从本地加载页面了,这样在网络上传输的数据就会大大减少,同时也减轻了服务器的负担。
如果不想让代理或浏览器缓存,加no-cache参数或private参数:
# expires 1d;
add_header Cache-Control no-cache;
add_header Cache-Control private;
如图:
第一次访问:
请求:
返回:
第二次请求:
返回:
依然返回的状态码是304,而在请求中比第一次多了If-None-Match头。所以实现浏览器缓存。如果这个值对不上,则缓存过期。
ETags和If-None-Match是一种常用的判断资源是否改变的方法。类似于Last-Modified和HTTP-If-Modified-Since。但是有所不同的是Last-Modified和HTTP-If-Modified-Since只判断资源的最后修改时间,而ETags和If-None-Match可以是资源任何的任何属性。
ETags和If-None-Match的工作原理是在HTTP Response中添加ETags信息。当客户端再次请求该资源时,将在HTTP Request中加入If-None-Match信息(ETags的值)。如果服务器验证资源的ETags没有改变(该资源没有改变),将返回一个304状态;否则,服务器将返回200状态,并返回该资源和新的ETags。
IIS上的ETag格式需要修改。
在大型多WEB集群时,使用ETag时有问题,所以有人建议使用WEB集群时不要使用ETag,其实很好解决,因为多服务器时,INode不一样,所以不同的服务器生成的ETag不一样,所以用户有可能重复下载(这时ETag就会不准)
如果服务器端同时设置了Etag和Expires 时,Etag原理同样,即与Last-Modified/Etag对应的HttpRequest Header:If-Modified-Since和If-None-Match。我们可以看到这两个Header的值和WebServer发出的 Last-Modified,Etag值完全一样;在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和 Etag之后,服务器才能返回304.
答案是同时使用,也就是说在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,服务器才能返回304.(不要陷入到底使用谁的问题怪圈)
浏览器缓存是提高用户体验和提升程序性能的一个很重要的途径,通过浏览器的缓存控制,可以对实时性要求不高的数据进行缓存,可以减少甚至不需要再次对服务器的请求就可以显示数据。
本文将介绍如果通过HTTP协议中的header来控制浏览器的缓存行为,建议大家在看的时候写代码试验下,这样对这些header的理解会更深一点。
HTTP协议定义了四个可以用来控制浏览器缓存的HTTP头,它们是:
Last-Modified
Expires
Pragma: no-cache
Cache-Control
下面分别介绍HTTP/1.0和HTTP/1.1协议下的缓存解决方法。
HTTP/1.0
在HTTP/1.0协议中,Last-Modified是控制缓存的一个非常重要的HTTP头。如果需要控制浏览器的缓存,服务器首先必须发送一个 以UTC时间为值的Last-Modifeid头,当第二次访问这个页面时,浏览器会发送一个If-Modified-Since头给服务器,让服务器判 断是否有必要更新内容,这个If-Modified-Since头的值就是上次访问页面时,浏览器发送的Last-Modifeid头的值。
Expires是HTTP/1.0中另外一个很重要的HTTP头,它表示缓存的存在时间,告诉客户端浏览器在这个时间之前不对服务器发送请求,而直接使用浏览器的缓存。
在HTTP/1.0中,可以使用Pragma: no-cache头来告诉浏览器不要缓存内容,它相当于HTTP/1.1中的Cache-Control:no-cache。
如果要使用HTTP/1.0协议来告诉客户端(包括任何中介代理)是否要缓存数据,可以使用以下代码,如果设置liftTime参数则告诉客户端数据缓存的生命期为lifeTime的值:
- function http_10_cache_headers($lifeTime = null){
- $gmtime = time();
- if ($lifeTime){
- $gmtime += $lifeTime;
- }else {
- header("Pragma: no-cache");
- }
- $gmtime = gmdate('D, d M Y H:i:s',$gmtime).' GMT';
- header("Last-Modified: $gmtime");
- header("Expires: $gmtime");
- }
HTTP/1.0协议的这种实现方式的缺点是,服务器和客户端的时间有可能是不同步的,这样会造成缓存的实现达不到预期效果。HTTP/1.1协议用Cache-Control头解决了这个问题。
HTTP/1.1
Cache-Control响应头的语法为:
Cache-Control = “Cache-Control” “:”; #缓存响应指令
缓存响应指令为一下几个中的任意一个:
- public
- private
- no-cache
- no-store
- no-transform
- must-revalidate
- proxy-revalidate
- max-age=时间
- s-maxage=时间
详细介绍一下这几个指令的具体含义:
- public 指示响应数据可以被任何客户端缓存
- private 指示响应数据可以被非共享缓存所缓存。这表明响应的数据可以被发送请求的浏览器缓存,而不能被中介所缓存
- no-cache 指示响应数据不能被任何接受响应的客户端所缓存
- no-store 指示所传送的响应数据除了不能被缓存,也不能存入磁盘。一般用于敏感数据,以免数据被复制。
- must-revalidate 指示所有的缓存都必须重新验证,在这个过程中,浏览器会发送一个If-Modified-Since头。如果服务器程序验证得出当前的响应数据为最新的数 据,那么服务器应当返回一个304 Not Modified响应给客户端,否则响应数据将再次被发送到客户端。
- proxy-revalidate 与must-revalidate相似,不同的是用来指示共享缓存。
- max-age 数据经过max-age设置的秒数后就会失效,相当于HTTP/1.0中的Expires头。如果在一次响应中同时设置了max-age和Expires,那么max-age将具有较高的优先级。
- s-maxage 与max-age相似,不同的是用来指示共享缓存。
了解这些指令后就可以根据不同的需求来发送不同的HTTP头。
根据响应的内容是否更改来确定是否发送新的响应数据:
- function validate_cache_headers($my_modtime)
- {
- $pretty_modtime = gmdate('D, d M Y H:i:s', $my_modtime) . ' GMT';
- if($_SERVER['IF_MODIFIED_SINCE'] == $gmt_mtime) {
- header("HTTP/1.1 304 Not Modified");
- exit;
- }
- else {
- header("Cache-Control: must-revalidate");
- header("Last-Modified: $pretty_modtime");
- }
- }
这个函数接受一个页面最后修改的时间作为参数,将它与浏览器发送的If-Modified-Since的时间比较,如果两者相同,说明缓存的数据版 本是最新的,就可以发送一个304状态码给浏览器,让它使用缓存的数据;否则,发送新的Last-Modified头和设置必须验证缓存的数据版本的 Cache-Control头。
如果想要让响应数据被代理缓存一分钟,可以这么做:
- function cache_novalidate($interval = 60)
- {
- $now = time();
- $pretty_lmtime = gmdate('D, d M Y H:i:s', $now) . ' GMT';
- $pretty_extime = gmdate('D, d M Y H:i:s', $now + $interval) . 'GMT';
- // 向后兼容HTTP/1.0
- header("Last Modified: $pretty_lmtime");
- header("Expires: $pretty_extime");
- // 支持HTTP/1.1
- header("Cache-Control: public,max-age=$interval");
- }
如果只想让浏览器缓存响应数据,可以这么做:
- function cache_browser($interval = 60)
- {
- $now = time();
- $pretty_lmtime = gmdate('D, d M Y H:i:s', $now) . ' GMT';
- $pretty_extime = gmdate('D, d M Y H:i:s', $now + $interval) . ' GMT';
- // 向后兼容HTTP/1.0
- header("Last Modified: $pretty_lmtime");
- header("Expires: $pretty_extime");
- // 支持HTTP/1.1
- header("Cache-Control: private,max-age=$interval,s-maxage=0");
- }
如果要让数据不被任何客户端缓存:
- function cache_none($interval = 60)
- {
- // 向后兼容HTTP/1.0
- header("Expires: 0");
- header("Pragma: no-cache");
- // 支持HTTP/1.1
- header("Cache-Control: no-cache,no-store,max-age=0,s-maxage=0,must-revalidate");
- }
当调用session_start()时,PHP会自动发送一个no-cache类的头来阻止缓存数据,
要注意的是:
通过POST方法发送的请求不能以如上所述的方式缓存。