使用gzip优化web应用(filter实现)

时间:2021-07-26 11:34:49

相关知识:

  gzip是http协议中使用的一种加密算法,客户端向web服务器端发出了请求后,通常情况下服务器端会将页面文件和其他资源,返回到客户端,客户端加载后渲染呈现,这种情况文件一般都比较大,如果开启Gzip ,那么服务器端响应后,会将页面,JS,CSS等文本文件或者其他文件通过高压缩算法将其压缩,然后传输到客户端,由客户端的浏览器负责解压缩与呈现。通常能节省40%以上的流量(一般都有60%左右),一些PHP,JSP文件也能够进行压缩。

1.实现:  

Tomcat 开启Gzip :

1.找到Tomcat 目录下的conf下的server.xml,并找到如下信息

Connector port="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true"

将它改成如下的形式(其实在上面代码的下面已经有了,将他们打开而已。):

<!-- Define a non-SSL HTTP/1.1 Connector on port 8080 --> <Connector port="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" compression="on" compressionMinSize="2048" noCompressionUserAgents="gozilla, traviata" compressableMimeType="text/html,text/xml" >

这样,就能够对html和xml进行压缩了,如果要压缩css 和 js,那么需要将

compressableMimeType=”text/html,text/xml”加入css和js:

<Connector port="8080" ......... compressableMimeType="text/html,text/xml,text/css,text/javascript" >

你甚至可以压缩图片:

compressableMimeType=”text/html,text/xml”加入css和js:

<Connector port="8080" ......... compressableMimeType="text/html,text/xml,text/css,text/javascript,image/gif,image/jpg" >

开启后重启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>