用 HttpServletResponseWrapper 实现 Etag 过滤器

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


最近对 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 是否与请求头一致,再进一步处理。

代码实现:



package com.chenlb.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.Date;
import java.util.zip.CRC32;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class EtagFilter implements Filter {

public void destroy() {}

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;

ByteArrayOutputStream baos = new ByteArrayOutputStream();
HttpServletResponseWrapper hsrw = new MyHttpResponseWrapper(servletResponse, baos);

chain.doFilter(request, hsrw);

hsrw.flushBuffer();

byte[] bytes = baos.toByteArray();

CRC32 crc = new CRC32();
crc.update(bytes);

String token = "w/\"" + crc.getValue() + '"';
servletResponse.setHeader("ETag", token);
// always store the ETag in the header
String previousToken = servletRequest.getHeader("If-None-Match");
if (previousToken != null && previousToken.equals(token)) {
// compare previous token with current one

System.out.println("ETag match: returning 304 Not Modified");
servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);
// use the same date we sent when we created the ETag the first time through
servletResponse.setHeader("Last-Modified", servletRequest.getHeader("If-Modified-Since"));
} else {
// first time through - set last modified time to now
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MILLISECOND, 0);
Date lastModified = cal.getTime();
servletResponse.setDateHeader("Last-Modified", lastModified.getTime());
System.out.println("Writing body content");
servletResponse.setContentLength(bytes.length);
ServletOutputStream sos = servletResponse.getOutputStream();
sos.write(bytes);
sos.flush();
sos.close();
}

}

public void init(FilterConfig config) throws ServletException {}

private static class MyHttpResponseWrapper extends HttpServletResponseWrapper {

ByteServletOutputStream servletOutputStream;
PrintWriter printWriter;

public MyHttpResponseWrapper(HttpServletResponse response, ByteArrayOutputStream buffer) {
super(response);
servletOutputStream = new ByteServletOutputStream(buffer);
}

public ServletOutputStream getOutputStream() throws IOException {
return servletOutputStream;
}

public PrintWriter getWriter() throws IOException {
if(printWriter == null) {
printWriter = new PrintWriter(servletOutputStream);
}
return printWriter;
}

public void flushBuffer() throws IOException {
servletOutputStream.flush();
if(printWriter != null) {
printWriter.flush();
}
}
}

private static class ByteServletOutputStream extends ServletOutputStream {

ByteArrayOutputStream baos;

public ByteServletOutputStream(ByteArrayOutputStream baos) {
super();
this.baos = baos;
}

public void write(int b) throws IOException {
baos.write(b);
}
}
}




web.xml 配置:



<filter>
<filter-name>etag</filter-name>
<filter-class>com.chenlb.http.EtagFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>etag</filter-name>
<url-pattern>*.jsp</url-pattern>
</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。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值