[翻译]Servlet Gzip Filter简介

原文地址:http://tutorials.jenkov.com/java-servlets/gzip-servlet-filter.html

简介

A GZip Servlet Filter can be used to GZip compress content sent to a browser from a Java web application. This text will explain how that works, and contains a GZip Servlet Filter you can use in your own Java web applications. If you do not know what a Servlet filter is, read my text on Servlet Filters.

gzip servlet filter 可用来压缩网页内容。本文将介绍其如何工作,以及如何使用gzip servlet filter 在你的web服务器中。如果不了解servlet filter是什么可以参考这篇文章:Servlet Filters.

为什么要使用gzip压缩网页内容

GZip compressing HTML, JavaScript, CSS etc. makes the data sent to the browser smaller. This speeds up the download. This is especially beneficial for mobile phones where internet bandwidth may be limited. GZip compressing content adds a CPU overhead on the server and browser, but it is still speeding up the total page load compared to not GZip compressing.

gzip 压缩html、Javascript,css等等文件,目的是为了是发送到浏览器的数据更小,加速下载,这对于限制了带宽的移动端作用特别大。当然gzip增加了server端和浏览器端的cpu的压力,但是总体来说还是加速了页面的载入。

Gzip HTTP Header

The browser includes the Accept-Encoding HTTP header in requests sent to an HTTP server (e.g. a Java web server). The content of the Accept-Encoding header tells what content encodings the browser can accept. If that header contains the value gzip in it, the browser can accept GZip compressed content. The server can then GZip compress the content sent back to the browser.
If the content sent back from the server is GZip compressed, the server includes the Content-EncodingHTTP header with the value gzip in the HTTP response. That way the browser knows that the content is GZip compressed.

浏览器端的HTTP报头包含Accept-Encoding在请求发送到一个HTTP服务器(例如Java Web Server),包含Accept-Encoding请求头的主体告知服务器端该浏览器可以接收编码主体,如果头部中包含gzip,那么浏览器可以接收通过gzip压缩的主体。服务端可以发送gzip压缩主体到浏览器端。

为什么是Gzip Servlet Filter

You could implement GZip compression in each and every Servlet or JSP in your application if you wanted to. But that gets clumsy.
The smart thing about a GZip Servlet filter is that it is executed before and after any Servlet, JSP, or even static files. That way you can create a single servlet filter that enables GZip compression for all content that needs it. The Servlets, JSPs etc. don’t even know that the content is being compressed, because it happens in the Servlet filter. The GZip Servlet filter enables GZip compression, sets the right HTTP headers, and makes sure that content written by Servlets, JSPs etc. is compressed.

你可以在在您的应用程序中每个Servlet或JSP实现GZip压缩,但这非常麻烦。一种巧妙的GZip Servlet过滤器是它之前和之后执行任何Servlet,JSP、甚至是静态文件。通过这种方式可以创建一个servlet过滤器,使用GZip压缩所有需要的内容。Servlet、jsp等或者不需要知道什么内容的文件被压缩,因为它存在Servlet过滤器中。GZip Servlet Filter支持GZip压缩,设置正确的HTTP标头,并确保内容由Servlet、jsp等被压缩。

设计Gzip Servlet Filter

The design of a GZip servlet filter looks like this:gzip-servlet-filter-1.png
First you need a Servlet filter class. That class is mapped to a set of URL’s in the web.xml file.
When an HTTP request arrives at the Servlet container which is mapped to the filter, the filter intercepts the request before it is handled by the Servlet, JSP etc. which the request is targeted at. The GZip servlet filter checks if the client (browser) can accept GZip compressed content. If yes, it enables compression of the response.
GZip compression of the response is enabled by wrapping the HttpServletResponse object in a GZipServletResponseWrapper. This wrapper is passed to the Servlet, JSP etc. which handles the request. When the Servlet, JSP etc. writes output to be sent to the browser, it does so to the response wrapper object. The Servlet, JSP etc. cannot see the difference between a real HttpServletResponse and the wrapper object. The response wrapper object then compresses the written content and writes the compressed content to the HttpServletResponse. Quite simple.

GZip servlet filter 的设计如上图所示,首先需要编写一个Servlet filter的类,在web.xml中设置映射到该类的URL.
当一个HTTP请求到达Servlet容器时候会被映射到这个filter上,filter会拦截之前处理的Servlet或者JSP等等,Gzip Servlet Filter会判断请求客户端是否接收Gzip压缩的内容,如果接收的话则允许压缩response。
GZip压缩的response是通过GZipServletResponseWrapper 封装 HttpServletResponse对象处理的,wrapper 处理Request传过来的Servlet、JSP等。当Servlet、JSP等写到输出并被发送到浏览器,它响应wrapper对象。Servlet和JSP等无法看到真实HttpServletResponse的区别和包装器对象,相应的wrapper对象然后压缩的内容并且写到HttpServletResponse中。

GZip Servlet Filter 编码

接下来的代码是Gzip Servlet Filter的代码,事实上并没有很多方式让你去写,直截了当。
代码包含三个类,其中GZipServletFilter拦截请求,检查客户端是否接收压缩,它在传递过滤链之前封装HttpServletResponse.
GZipServletResponseWrapperresponse的gzip封装.
GZipServletOutputStream 输出流.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class GZipServletFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void destroy() {
}

public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)

throws IOException, ServletException {


HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

if ( acceptsGZipEncoding(httpRequest) ) {
httpResponse.addHeader("Content-Encoding", "gzip");
GZipServletResponseWrapper gzipResponse =
new GZipServletResponseWrapper(httpResponse);
chain.doFilter(request, gzipResponse);
gzipResponse.close();
} else {
chain.doFilter(request, response);
}
}

private boolean acceptsGZipEncoding(HttpServletRequest httpRequest) {
String acceptEncoding =
httpRequest.getHeader("Accept-Encoding");

return acceptEncoding != null &&
acceptEncoding.indexOf("gzip") != -1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class GZipServletResponseWrapper extends HttpServletResponseWrapper {

private GZipServletOutputStream gzipOutputStream = null;
private PrintWriter printWriter = null;

public GZipServletResponseWrapper(HttpServletResponse response)
throws IOException {

super(response);
}

public void close() throws IOException {

//PrintWriter.close does not throw exceptions.
//Hence no try-catch block.
if (this.printWriter != null) {
this.printWriter.close();
}

if (this.gzipOutputStream != null) {
this.gzipOutputStream.close();
}
}


/**
* Flush OutputStream or PrintWriter
*
* @throws IOException
*/


@Override
public void flushBuffer() throws IOException {

//PrintWriter.flush() does not throw exception
if(this.printWriter != null) {
this.printWriter.flush();
}

IOException exception1 = null;
try{
if(this.gzipOutputStream != null) {
this.gzipOutputStream.flush();
}
} catch(IOException e) {
exception1 = e;
}

IOException exception2 = null;
try {
super.flushBuffer();
} catch(IOException e){
exception2 = e;
}

if(exception1 != null) throw exception1;
if(exception2 != null) throw exception2;
}

@Override
public ServletOutputStream getOutputStream() throws IOException {
if (this.printWriter != null) {
throw new IllegalStateException(
"PrintWriter obtained already - cannot get OutputStream");
}
if (this.gzipOutputStream == null) {
this.gzipOutputStream = new GZipServletOutputStream(
getResponse().getOutputStream());
}
return this.gzipOutputStream;
}

@Override
public PrintWriter getWriter() throws IOException {
if (this.printWriter == null && this.gzipOutputStream != null) {
throw new IllegalStateException(
"OutputStream obtained already - cannot get PrintWriter");
}
if (this.printWriter == null) {
this.gzipOutputStream = new GZipServletOutputStream(
getResponse().getOutputStream());
this.printWriter = new PrintWriter(new OutputStreamWriter(
this.gzipOutputStream, getResponse().getCharacterEncoding()));
}
return this.printWriter;
}


@Override
public void setContentLength(int len) {
//ignore, since content length of zipped content
//does not match content length of unzipped content.
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class GZipServletOutputStream extends ServletOutputStream {
private GZIPOutputStream gzipOutputStream = null;

public GZipServletOutputStream(OutputStream output)
throws IOException {

super();
this.gzipOutputStream = new GZIPOutputStream(output);
}

@Override
public void close() throws IOException {
this.gzipOutputStream.close();
}

@Override
public void flush() throws IOException {
this.gzipOutputStream.flush();
}

@Override
public void write(byte b[]) throws IOException {
this.gzipOutputStream.write(b);
}

@Override
public void write(byte b[], int off, int len) throws IOException {
this.gzipOutputStream.write(b, off, len);
}

@Override
public void write(int b) throws IOException {
this.gzipOutputStream.write(b);
}
}

web.xml中GZip Servlet Filter的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>com.myapp.GZipServletFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>