`

用 HttpServletResponseWrapper 实现 Etag 过滤器

 
阅读更多

原文出处:http://blog.chenlb.com/2009/07/use-httpservletresponsewrapper-implement-etag-filter.html

最近对 http caching 感兴趣,决定一步步学习之。现先来了解 Etag。

什么是“ETag”?

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

如果http 请求头 If-None-Match 的内容,与服务器对资源算出来的 etag 相同,就返回 304 响应。

下面来动动手,实现一个 etag 过虑器。原理:用 HttpServletResponseWrapper 把正常的页面输出到一个 byte 数组里,然后计算 etag,etag 是否与请求头一致,再进一步处理。

代码实现:

  1. package  com.chenlb.http;  
  2.   
  3. import  java.io.ByteArrayOutputStream;  
  4. import  java.io.IOException;  
  5. import  java.io.PrintWriter;  
  6. import  java.util.Calendar;  
  7. import  java.util.Date;  
  8. import  java.util.zip.CRC32;  
  9.   
  10. import  javax.servlet.Filter;  
  11. import  javax.servlet.FilterChain;  
  12. import  javax.servlet.FilterConfig;  
  13. import  javax.servlet.ServletException;  
  14. import  javax.servlet.ServletOutputStream;  
  15. import  javax.servlet.ServletRequest;  
  16. import  javax.servlet.ServletResponse;  
  17. import  javax.servlet.http.HttpServletRequest;  
  18. import  javax.servlet.http.HttpServletResponse;  
  19. import  javax.servlet.http.HttpServletResponseWrapper;  
  20.   
  21. public   class  EtagFilter  implements  Filter {  
  22.   
  23.     public   void  destroy() {}  
  24.   
  25.     public   void  doFilter(ServletRequest request, ServletResponse response,  
  26.             FilterChain chain) throws  IOException, ServletException {  
  27.   
  28.         HttpServletRequest servletRequest = (HttpServletRequest) request;  
  29.         HttpServletResponse servletResponse = (HttpServletResponse) response;  
  30.   
  31.         ByteArrayOutputStream baos = new  ByteArrayOutputStream();  
  32.         HttpServletResponseWrapper hsrw = new  MyHttpResponseWrapper(servletResponse, baos);  
  33.   
  34.         chain.doFilter(request, hsrw);  
  35.   
  36.         hsrw.flushBuffer();  
  37.   
  38.         byte [] bytes = baos.toByteArray();  
  39.   
  40.         CRC32 crc = new  CRC32();  
  41.         crc.update(bytes);  
  42.   
  43.         String token = "w/\""  + crc.getValue() +  '"' ;  
  44.         servletResponse.setHeader("ETag" , token);  
  45.         // always store the ETag in the header   
  46.         String previousToken = servletRequest.getHeader("If-None-Match" );  
  47.         if  (previousToken !=  null  && previousToken.equals(token)) {  
  48.             // compare previous token with current one         
  49.   
  50.             System.out.println("ETag match: returning 304 Not Modified" );  
  51.             servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);  
  52.             // use the same date we sent when we created the ETag the first time through   
  53.             servletResponse.setHeader("Last-Modified" , servletRequest.getHeader( "If-Modified-Since" ));  
  54.         } else   {  
  55.             // first time through - set last modified time to now   
  56.             Calendar cal = Calendar.getInstance();  
  57.             cal.set(Calendar.MILLISECOND, 0 );  
  58.             Date lastModified = cal.getTime();  
  59.             servletResponse.setDateHeader("Last-Modified" , lastModified.getTime());  
  60.             System.out.println("Writing body content" );  
  61.             servletResponse.setContentLength(bytes.length);  
  62.             ServletOutputStream sos = servletResponse.getOutputStream();  
  63.             sos.write(bytes);  
  64.             sos.flush();  
  65.             sos.close();  
  66.         }  
  67.   
  68.     }  
  69.   
  70.     public   void  init(FilterConfig config)  throws  ServletException {}  
  71.   
  72.     private   static   class  MyHttpResponseWrapper  extends  HttpServletResponseWrapper {  
  73.   
  74.         ByteServletOutputStream servletOutputStream;  
  75.         PrintWriter printWriter;  
  76.   
  77.         public  MyHttpResponseWrapper(HttpServletResponse response, ByteArrayOutputStream buffer) {  
  78.             super (response);  
  79.             servletOutputStream = new  ByteServletOutputStream(buffer);  
  80.         }  
  81.   
  82.         public  ServletOutputStream getOutputStream()  throws  IOException {  
  83.             return  servletOutputStream;  
  84.         }  
  85.   
  86.         public  PrintWriter getWriter()  throws  IOException {  
  87.             if (printWriter ==  null ) {  
  88.                 printWriter = new  PrintWriter(servletOutputStream);  
  89.             }  
  90.             return  printWriter;  
  91.         }  
  92.   
  93.         public   void  flushBuffer()  throws  IOException {  
  94.             servletOutputStream.flush();  
  95.             if (printWriter !=  null ) {  
  96.                 printWriter.flush();  
  97.             }  
  98.         }  
  99.     }  
  100.   
  101.     private   static   class  ByteServletOutputStream  extends  ServletOutputStream {  
  102.   
  103.         ByteArrayOutputStream baos;  
  104.   
  105.         public  ByteServletOutputStream(ByteArrayOutputStream baos) {  
  106.             super ();  
  107.             this .baos = baos;  
  108.         }  
  109.   
  110.         public   void  write( int  b)  throws  IOException {  
  111.             baos.write(b);  
  112.         }  
  113.     }  
  114. }  

web.xml 配置:

  1. < filter >   
  2.     < filter-name > etag </ filter-name >   
  3.     < filter-class > com.chenlb.http.EtagFilter </ filter-class >   
  4. </ filter >            
  5.   
  6. < filter-mapping >   
  7.     < filter-name > etag </ filter-name >   
  8.     < url-pattern > *.jsp </ url-pattern >   
  9. </ filter-mapping >   

测试环境是 tomcat 6.0.18。

用 httpwatch 可以观察效果。

etag-filter

etag-filter,点击放大

第二次请求(刷新),返回 304 。说明有效了。

过虑器同时还加了 Last-Modified 是为了兼容不支持 Etag 头的客户端。

参考:使用ETags减少Web应用带宽和负载

infoq 下载来的代码没试用通过,原因是没有 flush PrintWriter。虽然有 304,但返回的内容为空。

当然算 etag 可用其它算法,我这里用 crc32。infoq 例子中用 md5。

分享到:
评论

相关推荐

    使用Filter和HttpServletResponseWrapper进行页面缓存

    源代码 博文链接:https://msj.iteye.com/blog/179663

    serlvet 的过滤器实现缓存机制

    Servlet过滤器是Java Web开发中的一个重要组件,它允许在Servlet处理请求之前或之后对请求和响应进行拦截...通过合理的缓存策略和适当的配置,我们可以利用过滤器实现高效的数据缓存,降低服务器的负担,提高用户体验。

    STRUTS:filter过滤器

    例如,可以使用`HttpServletRequestWrapper`和`HttpServletResponseWrapper`来实现这一功能。要改变某个方法的行为,通常需要继承这些包装类并重写相应的方法。 #### 总结 过滤器在Struts框架中扮演着关键角色,...

    java过滤器中修改一个http请求的返回内容.docx

    配置过滤器到web.xml文件中,指定URL模式或过滤器链,以便在合适的时候调用你的`ResponseWrapper`。这样,每次匹配的请求经过过滤器时,都会触发自定义的响应处理逻辑。 总结起来,Java过滤器提供了一种机制,让...

    解决JSP字符串乱码的过滤器

    本教程将详细介绍如何通过实现一个过滤器(Filter)来解决JSP页面中的字符串乱码问题。 首先,我们需要理解字符编码的基础知识。字符编码是用来表示文本的一套规则,常见的有ASCII、GBK、UTF-8等。在Web应用中,...

    java过滤器中修改一个http请求的返回内容.pdf

    总结起来,Java过滤器通过HttpServletResponseWrapper和自定义的输出流包装类,可以在HTTP响应被发送到客户端之前对其进行修改,这在需要动态调整响应内容、添加额外信息或者实现其他自定义逻辑的场景下非常有用。...

    java filter打印请求返回参数

    为了实现在过滤器中获取请求体内容以及响应内容的功能,本方案主要采用了以下技术手段: 1. **对`HttpServletRequest`进行封装**:通过创建`HttpServletRequestWrapper`子类来包装原始请求对象,并重写`getReader()...

    使用filter实现url级别内存缓存示例

    4. **过滤器的使用**: - `cachedDo`方法是过滤器的核心,它在过滤器链中执行。首先,它获取当前请求的`CacheInfo`,如果存在且缓存过期,则创建`ProxyResponse`对象并继续过滤器链的执行。在过滤器链执行完成后,`...

    Spring Security 2.0 中文参考文档

    Spring Security的保护机制主要是通过一系列过滤器实现的,这些过滤器组成一个过滤链。当请求到达时,过滤链会按照顺序处理,执行相应的安全逻辑。 #### 2.1 `DelegatingFilterProxy` 这是Spring Security与...

    java,jsp,servlet 乱码

    - 过滤器可以通过`HttpServletRequestWrapper`和`HttpServletResponseWrapper`来修改请求和响应的编码。 7. **服务器配置**: - 除了在过滤器中设置,还可以在Tomcat的`server.xml`中全局设置Connector的`...

    spring-session简介及实现原理源码分析

    3. 在 doFilter 中,new 自定义的 Request 和 Response 的类,并把它们分别传递到过滤器链中 4. 把该 Filter 配置到过滤器链的第一个位置上 Spring-Session 的源码分析 Spring-Session 的源码分析可以从以下几个...

    springsecurity-thymeleaf.zip

    1. **Filter Chain**: Spring Security 使用一系列过滤器来处理请求,如`HttpServletRequestWrapper` 和 `HttpServletResponseWrapper`,这些过滤器负责身份验证、授权和其他安全操作。 2. **Authentication ...

    sping MVC 简单小例子

    2. **Controller**:控制器是业务逻辑的入口点,通常用注解@Controller标记,负责处理请求并返回模型数据。 3. **Model**:模型对象,包含了业务数据。 4. **View**:视图负责展示模型数据。Spring MVC支持多种...

    jsp实现将动态网页转换成静态页面的方法.docx

    ### JSP实现将动态网页转换成静态页面的方法 #### 概述 在Web开发中,为了提高网站的访问速度及减少服务器对数据库的频繁访问,一种常见的做法是将原本动态生成的内容转换为静态页面。本篇文章将深入探讨如何通过...

    9.设计模式-装饰器模式1

    1. **Servlet API**:在 Java Web 开发中,`HttpServletRequestWrapper` 和 `HttpServletResponseWrapper` 就是典型的装饰器模式应用,它们允许开发者在原始请求和响应对象的基础上添加额外的功能,如日志记录、安全...

    Java关于中文乱码问题的多种解决方法

    通过实现`Filter`接口,重写`doFilter()`方法,设置请求和响应的编码,如`HttpServletRequestWrapper`和`HttpServletResponseWrapper`。 8. 字符集理解: 对于不同的字符集,如GBK、UTF-8、GB2312等,理解它们之间...

    利用spring security 给cxf的业务方法添加保护

    这个过滤器应该实现`Filter`接口,并在`doFilter`方法中调用`HttpServletRequestWrapper`和`ServletResponseWrapper`,以便Spring Security可以检查请求的合法性。 ```java public class SpringSecurityCxfFilter ...

    servlet与jsp中文乱码处理

    - **配置Filter**:在web.xml中配置过滤器,指定需要拦截的URL模式。 - **处理乱码**:在`doFilter`方法中,对请求和响应进行编码设置。 ```java chain.doFilter(request, new HttpServletResponseWrapper...

Global site tag (gtag.js) - Google Analytics