普通青年用etag:后置拦截
-
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
-
HttpServletRequest servletRequest = (HttpServletRequest) req;
-
HttpServletResponse servletResponse = (HttpServletResponse) res;
-
-
ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
ETagResponseWrapper wrappedResponse = new ETagResponseWrapper(servletResponse, baos);
-
chain.doFilter(servletRequest, wrappedResponse);
-
-
byte[] bytes = baos.toByteArray();
-
-
String token = '"' + ETagComputeUtils.getMd5Digest(bytes) + '"';//对已经生成的视图内容计算md5值,消耗CPU
-
servletResponse.setHeader("ETag", token); // always store the ETag in the header
-
-
String previousToken = servletRequest.getHeader("If-None-Match");
-
if (previousToken != null && previousToken.equals(token)) { // 比较提交上来的且是上次从服务器获取的etags,相等则返回304,节省带宽
-
logger.debug("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 { // 赔了夫人又折兵,但是为了某些不支持1.1的浏览器还是要返回LM
-
Calendar cal = Calendar.getInstance();
-
cal.set(Calendar.MILLISECOND, 0);
-
Date lastModified = cal.getTime();
-
servletResponse.setDateHeader("Last-Modified", lastModified.getTime());
-
-
logger.debug("Writing body content");
-
servletResponse.setContentLength(bytes.length);
-
ServletOutputStream sos = servletResponse.getOutputStream();
-
sos.write(bytes);
-
sos.flush();
-
sos.close();
-
}
-
}
总结:简而言之就是牺牲性能换带宽,将已经经过底层操作(数据库操or外部接口之类的)获得的视图对象计算md5值,跟提交上来的etag进行比较,决定是发送304还是原本的web内容;缺点是对变更频率比较大的web内容没有良好的容忍性
文艺青年用etag:前置拦截
getToken方法从一个全局变量里取当前请求uri的计数器
-
public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {
-
String method = request.getMethod();
-
if (!"GET".equals(method))
-
return true;
-
-
String previousToken = request.getHeader("If-None-Match");
-
String token = getTokenFactory().getToken(request);//取得计数器
-
-
// compare previous token with current one
-
if ((token != null) && (previousToken != null && previousToken.equals('"' + token + '"'))) {//
-
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
-
// re-use original last modified timestamp
-
response.setHeader("Last-Modified", request.getHeader("If-Modified-Since"))
-
return false; // 不需要走处理链,直接返回304
-
}
-
-
// set header for the next time the client calls
-
if (token != null) {
-
response.setHeader("ETag", '"' + token + '"');
-
-
// first time through - set last modified time to now
-
Calendar cal = Calendar.getInstance();
-
cal.set(Calendar.MILLISECOND, 0);
-
Date lastModified = cal.getTime();
-
response.setDateHeader("Last-Modified", lastModified.getTime());
-
}
-
-
return true;//没什么损失,继续走处理链
-
}
全局变量是一个key-value的结构
-
public String getToken(HttpServletRequest request) {
-
String view = request.getRequestURI();
-
Integer count = counts.get(view);
-
if (count == null) {
-
return null;
-
}
-
-
return count.toString();
-
}
其中这个K-V计数器的变更是绑定在底层数据库的写函数上的(以Hibernate
3 LocalSessionFactoryBean为例)
-
public class DeleteHandler extends DefaultDeleteEventListener {
-
private ModifiedObjectTracker tracker;
-
-
public void onDelete(DeleteEvent event) throws HibernateException {
-
String viewname=eventname2view(event.getEntityName());//实体名转视图名字(比如说论坛某个帖子实质内容的ajax链接)
-
Integer count=counts.get(viewname);//获取计数器并自增
-
counts.put(view,count++);
-
}
-
-
public ModifiedObjectTracker getModifiedObjectTracker() {
-
return tracker;
-
}
-
public void setModifiedObjectTracker(ModifiedObjectTracker tracker) {
-
this.tracker = tracker;
-
}
-
}
总结:这么做不仅节省带宽,还节省CPU,更节省用户等待时间,有利于提高用户体验,可谓一举多得;需要注意的是,计数器那一块得设计好过期算法,那不然内存会不够你花
阅读(5448) | 评论(0) | 转发(0) |