zoukankan      html  css  js  c++  java
  • 用 HttpServletResponseWrapper 实现 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。

  • 相关阅读:
    mysql基础 MySql反向模糊查询
    mysql基础 函数
    html 标签的自定义属性应用
    mysql 分组后查询总行数,不使用子查询
    mysql基础 利用正则表达式判断数字
    网络工程师 教材目录
    Quatris
    BaseApplication Framework的skdCameraMan SdkTrayManager分析
    效率问题节点删除等
    ManulObject Ogre::RenderOperation::OT_TRIANGLE_STRIP
  • 原文地址:https://www.cnblogs.com/lhj588/p/2579188.html
Copyright © 2011-2022 走看看