ShallowEtagHeaderFilter

ShallowEtagHeaderFilter是Spring提供的支持ETag的一个servlet Filter。他是一个纯粹的Filter,所以可以很容易与web框架集成。

ShallowEtagHeaderFilter将创建一些很弱的(shallow)Etag()

The

ShallowEtagHeaderFilter filter creates so-called shallow ETags (和 deep ETags相对立)。这个Filter会将JSP等的内容缓存,生成MD5的key,然后在response中作为Etage的header返回给客户端。下次客户端对相同的资源(或者说相同的url)发出请求时,客户端会将之前生成的key作为If-None-Match的值发送到server端。 Filter会客户端传来的值和服务器上的做比较,如果相同,则返回304(关于304, 请见对Http304的理解);否则,将发送新的内容到客户端。

使用方法如下:

首先将这个filter引入到系统中,和其他的,如loginFilter类似,只要在配置文件(通常为web.xml)中加入如下代码即可:

view plaincopy to clipboardprint?<filter>   
<filter-name>etagFilter</filter-name>   
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>   
</filter>   
<filter-mapping>   
<filter-name>etagFilter</filter-name>   
<servlet-name>petclinic</servlet-name>   
</filter-mapping>  
<filter> 
<filter-name>etagFilter</filter-name> 
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class> 
</filter> 
<filter-mapping> 
<filter-name>etagFilter</filter-name> 
<servlet-name>petclinic</servlet-name> 
</filter-mapping> 



Java实现的代码如下(此段代码为Filter的实现,仅供参考,不用加到系统中)

view plaincopy to clipboardprint?ShallowEtagResponseWrapper responseWrapper = new ShallowEtagResponseWrapper(response);   
filterChain.doFilter(request, responseWrapper);   
   
byte[] body = responseWrapper.toByteArray();   
String responseETag = generateETagHeaderValue(body);   
response.setHeader(HEADER_ETAG, responseETag);   
   
String requestETag = request.getHeader(HEADER_IF_NONE_MATCH);   
if (responseETag.equals(requestETag)) {   
    if (logger.isTraceEnabled()) {   
        logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304");   
    }   
    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);   
}   
else {   
    if (logger.isTraceEnabled()) {   
        logger.trace("ETag [" + responseETag + "] not equal to If-None-Match [" + requestETag +   
                "], sending normal response");   
    }   
    response.setContentLength(body.length);   
    FileCopyUtils.copy(body, response.getOutputStream());   

ShallowEtagResponseWrapper responseWrapper = new ShallowEtagResponseWrapper(response); 
filterChain.doFilter(request, responseWrapper); 
 
byte[] body = responseWrapper.toByteArray(); 
String responseETag = generateETagHeaderValue(body); 
response.setHeader(HEADER_ETAG, responseETag); 
 
String requestETag = request.getHeader(HEADER_IF_NONE_MATCH); 
if (responseETag.equals(requestETag)) { 
    if (logger.isTraceEnabled()) { 
        logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304"); 
    } 
    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); 

else { 
    if (logger.isTraceEnabled()) { 
        logger.trace("ETag [" + responseETag + "] not equal to If-None-Match [" + requestETag + 
                "], sending normal response"); 
    } 
    response.setContentLength(body.length); 
    FileCopyUtils.copy(body, response.getOutputStream()); 
}

-------------------------------------------------

最近和同事一起看Web的Cache问题,又进一步理解了 HTTP 中的 304 又有了一些了解。 304 的标准解释是:

Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
如果客户端在请求一个文件的时候,发现自己缓存的文件有 Last Modified ,那么在请求中会包含 If Modified Since ,这个时间就是缓存文件的 Last Modified 。因此,如果请求中包含 If Modified Since,就说明已经有缓存在客户端。只要判断这个时间和当前请求的文件的修改时间就可以确定是返回 304 还是 200 。对于静态文件,例如:CSS、图片,服务器会自动完成 Last Modified 和 If Modified Since 的比较,完成缓存或者更新。但是对于动态页面,就是动态产生的页面,往往没有包含 Last Modified 信息,这样浏览器、网关等都不会做缓存,也就是在每次请求的时候都完成一个 200 的请求。

因此,对于动态页面做缓存加速,首先要在 Response 的 HTTP Header 中增加 Last Modified 定义,其次根据 Request 中的 If Modified Since 和被请求内容的更新时间来返回 200 或者 304 。虽然在返回 304 的时候已经做了一次数据库查询,但是可以避免接下来更多的数据库查询,并且没有返回页面内容而只是一个 HTTP Header,从而大大的降低带宽的消耗,对于用户的感觉也是提高。

当这些缓存有效的时候,通过 HttpWatch 查看一个请求会得到这样的结果:
第一次访问 200(success)
鼠标点击二次访问 (Cache)
按F5刷新 304
按Ctrl+F5强制刷新 200

如果是这样的就说明缓存真正有效了。以上就是我对 HTTP 304 的一个理解。

-----------------------------------------------

Etag是什么:


Etag 是URL的Entity Tag,用于标示URL对象是否改变,区分不同语言和Session等等。具体内部含义是使服务器控制的,就像Cookie那样。

  HTTP协议规格说明定义ETag为“被请求变量的实体值” 。另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端。



实例详解:


也许读者们看完这个概念,还是搞不清楚这个etag响应头消息到底是干什么的,具体如何使用,所以接下来的篇幅我会用一个实际的例子来详细阐述etag的妙用。





想必许多网友都有订阅某些大虾的RSS的习惯吧,但是大虾也是人,也要吃饭睡觉打豆豆,所以不可能无时无刻的在从事文学创作,因此一般产量较高的大虾也许平均每天能更新两篇已经是不错了,但是网友们却总是不断的去刷新RSS订阅的内容,期望在下次刷新中又有新的劲爆文章出现,如果我们每次刷新,都要从服务器端重新获取内容(事实上,几乎一天内95%以上的刷新返回的都是相同内容,因为刚才也说了,大虾一般一天也就出一两篇新文章而已,所以大部分时间内,内容都是相同的),如果订阅量相当巨大,这对于服务器的压力还是带宽都是一个严重的挑战。其实真正需要服务器重新返回内容是大虾们更新了新的文章后,而其他时间我们无论怎么刷新服务器最好能做到不需返回任何数据,这才是一个比较好的方案,而我们的主角etag响应头的出现正是为了解决这个问题。





浏览器端实现



当我们订阅了某大虾的RSS,如图,我们点击左下角“RSS”按钮:




然后我们进入了rss订阅的相关页面,此时我们打开tamper data查看下http请求和响应的一些内容(这里我们截获http请求和响应信息的工具是基于FF浏览器的tamper data插件,如果您对这个插件不熟悉,请参考我的另外一篇介绍该工具的文章--  Tamper Data安装与使用简介 )。如下图:






从上图我们可以看到,服务器端返回响应值为200,并发送回了相应的网页内容,在http响应头中有etag消息头,值为

Etag="fca75d26f6dc8111a7d1b24e9debd652",我们不需要去关心这个响应头的内容是什么,我们只需要将这这个

头信息的值记录下来,随便创建一个notepad之类的东西保存好,以备接下来的实验使用。



然后我们刷新,希望能从webserver端获取该大虾新的文章,这时候我们用tamper data查看下http头中有哪些内容,如下图:






从此图中我们可以看到If-None-Match="fca75d26f6dc8111a7d1b24e9debd652",而该值的内容和我们先前第一次

刷新返回的http响应中的etag响应头的值完全一样。



如果某大虾并没在这段时间内发表任何文章,于是webserver端的rss文件没有任何变化,于是If-None-Match值和server端

的etag值相比较完全相等,这时候服务器就会认为客户端已经有最新的rss文件内容的缓存了,于是服务器就会发送一个 响应码

为“304”的http响应,304响应想必大家都知道是什么意思了,没错,他不包含任何响应的内容,只是提示客户端缓存的内容是最新的,

如下图








如果某大虾刚发表了一篇新的文章,因此在webserver中的rss的内容发生了改变,因此他的etag值就会发生改变,于是服务器会拿http请求中的If-None-Match的

值和改变和的etag值做对比,显然不正确的,于是webserver就会发送一个新的rss内容给客户端,这里我不能强制要求某大虾来配合我们的实验去立马发表新文章,

所以我们就变相做,也就是我们故意修改http请求头中的If-None-Match的值,这样就和服务器端的etag就不会匹配了,显然这时候服务器就会受骗发送一份“新”的

rss内容回来,如下图:












这里我们将http请求中的If-None-Match的值改为了"modifiedForOurTest", 显然与响应中的Etag="fca75d26f6dc8111a7d1b24e9debd652"不同,因此服务器就被受骗认为浏览器没有最新的文件内容,于是就返回了全新的响应内容(状态码200),而事实上浏览器端是有最新的内容的缓存的。





结束语:



好了,到这里,对这个etag响应头的讲解就结束了,如果反应不错,下一篇我们将介绍并实例详解cache-control的用法,敬请期待:)

--------------------------------------------------



你可能感兴趣的:(filter)