使用gzip优化web应用(filter实现)
相关知识:
gzip是http协议中使用的一种加密算法,客户端向web服务器端发出了请求后,通常情况下服务器端会将页面文件和其他资源,返回到客户端,客户端加载后渲染呈现,这种情况文件一般都比较大,如果开启Gzip ,那么服务器端响应后,会将页面,JS,CSS等文本文件或者其他文件通过高压缩算法将其压缩,然后传输到客户端,由客户端的浏览器负责解压缩与呈现。通常能节省40%以上的流量(一般都有60%左右),一些PHP,JSP文件也能够进行压缩。
1.实现:
Tomcat 开启Gzip :
1.找到Tomcat 目录下的conf下的server.xml,并找到如下信息
将它改成如下的形式(其实在上面代码的下面已经有了,将他们打开而已。):
这样,就能够对html和xml进行压缩了,如果要压缩css 和 js,那么需要将
compressableMimeType=”text/html,text/xml”加入css和js:
你甚至可以压缩图片:
compressableMimeType=”text/html,text/xml”加入css和js:
开启后重启Tomcat ,通过浏览器查看headers信息就能看到是否开启(firebug中有),如果开启了,那么transfer-encoding就会是Gzip,否则就是chunked。
2.在代码级别完成web应用的gzip压缩的开启:
1.Wrapper 用来包装HttpServletResponse 对象
package com.shop.gzip; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; public class Wrapper extends HttpServletResponseWrapper { public static final int OT_NONE = 0, OT_WRITER = 1, OT_STREAM = 2; private int outputType = OT_NONE; private ServletOutputStream output = null; private PrintWriter writer = null; private ByteArrayOutputStream buffer = null; public Wrapper(HttpServletResponse resp) throws IOException { super(resp); buffer = new ByteArrayOutputStream(); } public PrintWriter getWriter() throws IOException { if (outputType == OT_STREAM) throw new IllegalStateException(); else if (outputType == OT_WRITER) return writer; else { outputType = OT_WRITER; writer = new PrintWriter(new OutputStreamWriter(buffer, getCharacterEncoding())); return writer; } } public ServletOutputStream getOutputStream() throws IOException { if (outputType == OT_WRITER) throw new IllegalStateException(); else if (outputType == OT_STREAM) return output; else { outputType = OT_STREAM; output = new WrappedOutputStream(buffer); return output; } } public void flushBuffer() throws IOException { if (outputType == OT_WRITER) writer.flush(); if (outputType == OT_STREAM) output.flush(); } public void reset() { outputType = OT_NONE; buffer.reset(); } public byte[] getResponseData() throws IOException { flushBuffer(); return buffer.toByteArray(); } class WrappedOutputStream extends ServletOutputStream { private ByteArrayOutputStream buffer; public WrappedOutputStream(ByteArrayOutputStream buffer) { this.buffer = buffer; } public void write(int b) throws IOException { buffer.write(b); } public byte[] toByteArray() { return buffer.toByteArray(); } } }
2.过滤器
package com.shop.gzip; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPOutputStream; 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; public class GZipFilter implements Filter { public void destroy() { } /** * 判断浏览器是否支持GZIP * @param request * @return */ private static boolean isGZipEncoding(HttpServletRequest request){ boolean flag=false; String encoding=request.getHeader("Accept-Encoding"); if(encoding.indexOf("gzip")!=-1){ flag=true; } return flag; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse resp = (HttpServletResponse) response; HttpServletRequest req=(HttpServletRequest)request; if(isGZipEncoding(req)){ Wrapper wrapper = new Wrapper(resp); chain.doFilter(request, wrapper); byte[] gzipData = gzip(wrapper.getResponseData()); resp.addHeader("Content-Encoding", "gzip"); resp.setContentLength(gzipData.length); ServletOutputStream output = response.getOutputStream(); output.write(gzipData); output.flush(); } else { chain.doFilter(request, response); } } public void init(FilterConfig arg0) throws ServletException { } private byte[] gzip(byte[] data) { ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(10240); GZIPOutputStream output = null; try { output = new GZIPOutputStream(byteOutput); output.write(data); } catch (IOException e) { } finally { try { output.close(); } catch (IOException e) { } } return byteOutput.toByteArray(); } }
3.在web.xml中配置 GZipFilter,当我们访问应用中以.do结尾的资源的使用,服务器端就开启http gzip压缩,将压缩后的信息通过http 协议传递给浏览器.
<filter> <filter-name>ecsideExport</filter-name> <filter-class>com.web.servlet.GZipFilter</filter-class> </filter> <filter-mapping> <filter-name>ecsideExport</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
番外:使用Jayson Falkner的过滤器
Jayson Falkner 在他的Two Servlet Filters Every Web Application Should Have(http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html?page=1 ) 里面介绍了2个提高Java Web Application 效能的Servlet。这里记一下其中的GZIPFilter。
GZIPFilter可以用gzip的方式压缩HTTP
Response的内容,从而在Server端加快了响应回复的速度(gzip压缩过程需耗用CPU资源,却大幅减轻了网卡的处理负担),在Client端缩短了页面刷新时间,在网络内减少了数据流量。另一方面,因为采用的Filter的方式,无需改动应用现有的代码就可引入该功能。
以上Filter的source code在这里(http://www.onjava.com/onjava/2003/11/19/examples/jspbook.zip
)下载。当然也可以在这里(http://www.onjava.com/onjava/2003/11/19/examples/jspbook.jar
)下载现成的JAR。
作者定义了3个类来实现这个压缩功能:GZIPFilter, GZIPResponseWrapper, GZIPResponseStream。类图大致如下:
这3个类要做的,就是在Server将响应内容装入HTTP
Response之前,先对内容进行GZIP压缩。其中GZIPFilter实现了javax.servlet.Filter接口,在Java
Web Container处理HTTP
Response的过程中便可以挂载使用。另外2个类(GZIPResponseWrapper,GZIPResponseStream)基本上是辅助类,真正的压缩动作是在GZIPResponseStream中由java.util.zip.GZIPOutputStream来完成的,除此还有其它的一些方法,感觉似乎并没有全部用到。这部分还需要另外再研究。
要布署这个Filter也很简单。只要将JAR放至应用的library目录,并且在应用的布署配置文件web.xml中加入
GZIPFilter
com.jspbook.GZIPFilter
GZIPFilter
/*.jsp
然后启动Server就可以了。
GZIPFilter压缩HTTP Response内容的作用非常明显。作者在source code的包里面另外放了TestGZIP.jsp,这是用来显示GZIPFilter的压缩效果的JSP。你可以找一支产生大量内容的目标JSP来测验一下,就像下面这支show_response_compressed.jsp:
<%@ page import="java.io.*" %> <%@ page import="java.util.zip.*" %> <% String title = "Show Compressed Response"; int size = 100000; out.println("<HTML>/n" + "<HEAD><TITLE>" + title + "</TITLE></HEAD>/n" + "<BODY BGCOLOR=/"#FDF5E6/">/n" + "<H1 ALIGN=/"CENTER/">" + title + " SIZE="+size+"</H1>/n"); String line = "Blah, blah, blah, blah, blah. " + "Yadda, yadda, yadda, yadda."; for(int i=0; i<size; i++) { out.println(line); } out.println("</BODY></HTML>"); %>
运行show_response_compressed.jsp可以产生类似如下截图中的页面。
运行TestGZIP.jsp,在URL中填入show_response_compressed.jsp 所在的地址,提交后即可得到如下结果。
可以看到,未经压缩的HTTP Response数据量达到了2,950,086 bytes (2.9MB),而压缩后的数据量仅有8,687 bytes(8 KB),压缩比高达99.7%!
因为是在一台机器上作的验证,所以在响应时间的改善方面感觉不是很明显。同样的,如果是在Intranet环境内,则这种效果也不会很明显。如果是在Internet上测试,改善的效果应该会比较明显。
附上源码
/* * Copyright 2003 Jayson Falkner (jayson@jspinsider.com) * This code is from "Servlets and JavaServer pages; the J2EE Web Tier", * http://www.jspbook.com. You may freely use the code both commercially * and non-commercially. If you like the code, please pick up a copy of * the book and help support the authors, development of more free code, * and the JSP/Servlet/J2EE community. */ package cn.javass.common.web.filter; /*使用gzip优化web应用(filter实现)*/ import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; public class GZIPFilter implements Filter { private static Logger log = LoggerFactory.getLogger(GZIPFilter.class); public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { if (req instanceof HttpServletRequest) { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String ae = request.getHeader("accept-encoding"); if (ae != null && ae.indexOf("gzip") != -1) { log.debug("GZIP supported, compressing."); GZIPResponseWrapper wrappedResponse = new GZIPResponseWrapper(response); chain.doFilter(req, wrappedResponse); wrappedResponse.finishResponse(); return; } chain.doFilter(req, res); } } public void init(FilterConfig filterConfig) { // noop } public void destroy() { // noop } }
/* * Copyright 2003 Jayson Falkner (jayson@jspinsider.com) * This code is from "Servlets and JavaServer pages; the J2EE Web Tier", * http://www.jspbook.com. You may freely use the code both commercially * and non-commercially. If you like the code, please pick up a copy of * the book and help support the authors, development of more free code, * and the JSP/Servlet/J2EE community. */ package cn.javass.common.web.filter; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.zip.GZIPOutputStream; public class GZIPResponseStream extends ServletOutputStream { protected ByteArrayOutputStream baos = null; protected GZIPOutputStream gzipstream = null; protected boolean closed = false; protected HttpServletResponse response = null; protected ServletOutputStream output = null; public GZIPResponseStream(HttpServletResponse response) throws IOException { super(); closed = false; this.response = response; this.output = response.getOutputStream(); baos = new ByteArrayOutputStream(); gzipstream = new GZIPOutputStream(baos); } @Override public void close() throws IOException { if (closed) { throw new IOException("This output stream has already been closed"); } gzipstream.finish(); byte[] bytes = baos.toByteArray(); // response.addHeader("Content-Length", Integer.toString(bytes.length)); response.addHeader("Content-Encoding", "gzip"); output.write(bytes); output.flush(); output.close(); closed = true; } @Override public void flush() throws IOException { if (closed) { throw new IOException("Cannot flush a closed output stream"); } gzipstream.flush(); } @Override public void write(int b) throws IOException { if (closed) { throw new IOException("Cannot write to a closed output stream"); } gzipstream.write((byte) b); } @Override public void write(byte b[]) throws IOException { write(b, 0, b.length); } @Override public void write(byte b[], int off, int len) throws IOException { if (closed) { throw new IOException("Cannot write to a closed output stream"); } gzipstream.write(b, off, len); } public boolean closed() { return (this.closed); } public void reset() { //noop } }
/* * Copyright 2003 Jayson Falkner (jayson@jspinsider.com) * This code is from "Servlets and JavaServer pages; the J2EE Web Tier", * http://www.jspbook.com. You may freely use the code both commercially * and non-commercially. If you like the code, please pick up a copy of * the book and help support the authors, development of more free code, * and the JSP/Servlet/J2EE community. */ package cn.javass.common.web.filter; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; public class GZIPResponseWrapper extends HttpServletResponseWrapper { protected HttpServletResponse origResponse = null; protected ServletOutputStream stream = null; protected PrintWriter writer = null; public GZIPResponseWrapper(HttpServletResponse response) { super(response); origResponse = response; } public ServletOutputStream createOutputStream() throws IOException { return (new GZIPResponseStream(origResponse)); } public void finishResponse() { try { if (writer != null) { writer.close(); } else { if (stream != null) { stream.close(); } } } catch (IOException e) { } } @Override public void flushBuffer() throws IOException { stream.flush(); } @Override public ServletOutputStream getOutputStream() throws IOException { if (writer != null) { throw new IllegalStateException("getWriter() has already been called!"); } if (stream == null) stream = createOutputStream(); return (stream); } @Override public PrintWriter getWriter() throws IOException { if (writer != null) { return (writer); } if (stream != null) { throw new IllegalStateException("getOutputStream() has already been called!"); } stream = createOutputStream(); writer = new PrintWriter(new OutputStreamWriter(stream, "UTF-8")); return (writer); } @Override public void setContentLength(int length) { } }
web.xml
<!--GZIP文件压缩的应用 --> <filter> <filter-name>GZIPFilter</filter-name> <filter-class>cn.javass.common.web.filter.GZIPFilter</filter-class> </filter> <filter-mapping> <filter-name>GZIPFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>