相关知识:
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);}@Overridepublic 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;}@Overridepublic void flush() throws IOException {if (closed) {throw new IOException("Cannot flush a closed output stream");}gzipstream.flush();}@Overridepublic void write(int b) throws IOException {if (closed) {throw new IOException("Cannot write to a closed output stream");}gzipstream.write((byte) b);}@Overridepublic void write(byte b[]) throws IOException {write(b, 0, b.length);}@Overridepublic 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) {}}@Overridepublic void flushBuffer() throws IOException {stream.flush();}@Overridepublic ServletOutputStream getOutputStream() throws IOException {if (writer != null) {throw new IllegalStateException("getWriter() has already been called!");}if (stream == null) stream = createOutputStream();return (stream);}@Overridepublic 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);}@Overridepublic 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>