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.
Table of contents:
Why GZip Compress Content?
GZip HTTP Headers
Why a GZip HTTP Servlet Filter?
GZip Servlet Filter Design
GZip Servlet Filter Code
GZip Servlet Filter web.xml Configuration
Why GZip Compress Content?
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 HTTP Headers
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-Encoding HTTP header with the value gzip in the HTTP response. That way the browser knows that the content is GZip compressed.
Why a 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.
GZip Servlet Filter Design
The design of a GZip servlet filter looks like this:
The GZip Servlet Filter design.
The GZip Servlet Filter design.
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 Code
Here is the GZip Servlet filter code. There are not really that many ways you can write it. It is pretty straightforward.
The code consists of 3 classes. A GZipServletFilter, a GZipServletResponseWrapper and a GZipServletOutputStream.
The GZipServletOutputStream is what compresses the content written to it. It does so by using a GZIPOutputStream internally, which is a standard Java class.
When the GZipServletResponseWrapper gives back an OutputStream or PrintWriter to a Servlet or JSP, it is either a GZipServletOutputStream or a PrintWriter that writes to the GZipServletOutputStream which is returned.
The GZipServletFilter is what intercepts the requests, checks if the client accepts compression or not, and enables compression if it does. It does so by wrapping the HttpServletResponse in a GZipServletResponseWrapper before passing it down the filter chain.
Here are all three classes:
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;
}
}
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. Thus, the call does not need
//be inside a 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.
}
}
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);
}
}
GZip Servlet Filter web.xml Configuration
In order to activate the GZip Servlet filter in your Java web application, you need the configuration below. Remember to replace the class name with the fully qualified name of your own GZip Servlet filter class. The filter mappings determine what HTTP requests the filter is activated for.
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>com.jenkov.tutorials.filters.gzip.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>