How to compress response HTML in Java web application
Introduction
Nowadays it's important to keep the size of a web application response to the minimum. By reducing the application response size we will increase the overall response data transfer speed while decreasing the required bandwidth.
In this tutorial we will see how to minify the response of a Java web application. We will be using the htmlcompressor project:
http://code.google.com/p/htmlcompressor
We will use the following Maven dependencies (htmlcompressor is available at the Maven Central Repository):
<dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.googlecode.htmlcompressor</groupId> <artifactId>htmlcompressor</artifactId> <version>1.5.2</version> </dependency> </dependencies>
The HTML compressor
The HTML compressor we will be using has a lot of configuration options. You may refer to the project page mentioned in the introduction. A simple initialization of the HTML compressor may look like the following:
HtmlCompressor compressor = new HtmlCompressor();
As the documentation states the compressor initialization may be expensive so one should initialize a single compressor instance and use it across multiple requests.
Another important detail is that the compressor instance is thread safe as long as we don't use statistics generation. This means that if you don't use the statistics generation feature you may use a single compressor instance to process multiple requests simultaneously.
The compression filter
We will use a standard Java web application filter in order to compress our web application responses:
package com.byteslounge.web.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; import com.googlecode.htmlcompressor.compressor.HtmlCompressor; @WebFilter( filterName = "CompressResponseFilter", urlPatterns = { "/pages/*" } ) public class CompressResponseFilter implements Filter { private HtmlCompressor compressor; @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { CharResponseWrapper responseWrapper = new CharResponseWrapper( (HttpServletResponse) resp); chain.doFilter(req, responseWrapper); String servletResponse = new String(responseWrapper.toString()); resp.getWriter().write(compressor.compress(servletResponse)); } @Override public void init(FilterConfig config) throws ServletException { compressor = new HtmlCompressor(); compressor.setCompressCss(true); compressor.setCompressJavaScript(true); } @Override public void destroy() { } }
We have just defined a standard web application filter that initializes the compressor during filter initialization. One may see that we configured the compressor to enable inline CSS and JavaScript compression.
This tutorial will not cover details that are specific to servlets and filters, but as you probably already know a servlet will write the response directly to the client by default. In short this means that when the execution returns to our filter after chain.doFilter() the response would have already been streamed to the client.
In this case we have to override this behaviour: We need the response before it's streamed to the client in order to compress it.
This is achieved using an HttpServletResponseWrapper. The CharResponseWrapper we are using extends this class and its definition follows next:
package com.byteslounge.web.filter; import java.io.CharArrayWriter; import java.io.PrintWriter; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; public class CharResponseWrapper extends HttpServletResponseWrapper { private final CharArrayWriter output; @Override public String toString() { return output.toString(); } public CharResponseWrapper(HttpServletResponse response) { super(response); output = new CharArrayWriter(); } @Override public PrintWriter getWriter() { return new PrintWriter(output); } }
Once again, this tutorial will not focus in servlet or filter details, but we may say that by using the wrapper we will make the container write the response into the underlying wrapper data holding structure (CharArrayWriter) instead of writing it directly to the client.
Since we are using the wrapper we may obtain the full response in our filter before it's sent to the client, and then apply the compression.
Downloadable sample
At the end of this page you may find a downloadable sample that illustrates this scenario. The sample consists of a Java web application containing a single page that will be compressed when accessed ([app-context-root]/pages/page.jsp).
If you inspect the testing page HTML source in your web browser after accessing the URL you will observe that the HTML is compressed within a single line.