9.11. Example: A Compression Filter

Several recent browsers can handle gzipped content, automatically uncompressing documents that have gzip as the value of the Content-Encoding response header and then treating the result as though it were the original document. Sending such compressed content can be a real time saver because the time required to compress the document on the server and then uncompress it on the client is typically dwarfed by the savings in download time, especially when dialup connections are used. For example, Listing 9.21 shows a servlet that has very long, repetitive, plain text output: a ripe candidate for compression. If gzip could be applied, it could compress the output by a factor of over 300!

However, although most browsers support this type of encoding, a fair number do not. Sending compressed content to browsers that don’t support gzip encoding results in a totally garbled result. Browsers that support content encoding include most versions of Netscape for Unix, most versions of Internet Explorer for Windows, and Netscape 4.7 and later for Windows. So, this compression cannot be done blindly—it is only valid for clients that use the Accept-Encoding request header to specify that they support gzip.

A compression filter can use the CharArrayWrapper of Section 9.9 to compress content when the browser supports such a capability. Accomplishing this task requires the following:

  1. A class that implements the Filter interface. This class is called CompressionFilter and is shown in Listing 9.20. The init method stores the FilterConfig object in a field in case subclasses need access to the servlet context or filter name. The body of the destroy method is left empty.

  2. A wrapped response object. The doFilter method wraps the ServletResponse object in a CharArrayWrapper and passes that wrapper to the doFilter method of the FilterChain object. After this call completes, all other filters and the final resource have executed and the output is inside the wrapper. So, the original doFilter extracts a character array that represents all of the resource’s output. If the client indicates that it supports compression (i.e., has gzip as one of the values of its Accept-Encoding header), the filter attaches a GZIPOutputStream to a ByteArrayOutputStream, copies the character array into that stream, and sets the Content-Encoding response header to gzip. If the client does not support gzip, the unmodified character array is copied to the ByteArrayOutputStream. Finally, doFilter sends that result to the client by writing the entire byte array (possibly compressed) to the OutputStream that is associated with the original response.

  3. Registration with long servlet. First, the filter element of web.xml (Listing 9.22) associates the name CompressionFilter with the class moreservlets.filters.CompressionFilter. Next, the filter-mapping element uses a servlet-name of LongServlet so that the filter fires each time that long servlet (Listing 9.21) is requested. The servlet and servlet-mapping elements assign the name LongServlet to the servlet and specify the URL that corresponds to the servlet.

  4. Disablement of the invoker servlet. This operation is shown in Section 9.2 and is not repeated here.

When the filter is attached, the body of the servlet is reduced three hundred times and the time to access the servlet on a 28.8K modem is reduced by more than a factor of ten (more than 50 seconds uncompressed; less than 5 seconds compressed). A huge savings! However, two small warnings are in order here.

First, there is a saying in the software industry that there are three kinds of lies: lies, darn lies, and benchmarks. The point of this maxim is that people always rig benchmarks to show their point in the most favorable light possible. I did the same thing by using a servlet with long simple output and using a slow modem connection. So, I’m not promising that you will always get a tenfold performance gain. But, it is a simple matter to attach or detach the compression filter. That’s the beauty of filters. Try it yourself and see how much it buys you in typical usage conditions.

Second, although the specification does not officially mandate that you set response headers before calling the doFilter method of the FilterChain, some servers (e.g., ServletExec 4.1) require you to do so. This is to prevent you from attempting to set a response header after a resource has sent content to the client. So, for portability, be sure to set response headers before calling chain.doFilter.

Core Warning

If your filter sets response headers, be sure it does so before calling the doFilter method of the FilterChain object.


Figure 9-9. The LongServlet. The content is more than three hundred times smaller when gzip is used, resulting in more than a tenfold speedup when the servlet is accessed with a 28.8K modem.


Listing 9.20. CompressionFilter.java
package moreservlets.filters; 

import java.io.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 
import java.util.zip.*; 

/** Filter that compresses output with gzip 
 *  (assuming that browser supports gzip). 
 */ 

public class CompressionFilter implements Filter {
  private FilterConfig config; 

  /** If browser does not support gzip, invoke resource 
   *  normally. If browser <I>does</I> support gzip, 
   *  set the Content-Encoding response header and 
   *  invoke resource with a wrapped response that 
   *  collects all the output. Extract the output 
   *  and write it into a gzipped byte array. Finally, 
   *  write that array to the client's output stream. 
   */ 
  public void doFilter(ServletRequest request, 
                       ServletResponse response, 
                       FilterChain chain) 
      throws ServletException, IOException {
    HttpServletRequest req = (HttpServletRequest)request; 
    HttpServletResponse res = (HttpServletResponse)response; 
    if (!isGzipSupported(req)) {
      // Invoke resource normally. 
      chain.doFilter(req,res); 
    } else {
      // Tell browser we are sending it gzipped data. 
      res.setHeader("Content-Encoding", "gzip"); 

      // Invoke resource, accumulating output in the wrapper. 
      CharArrayWrapper responseWrapper =
						new CharArrayWrapper(res);
						chain.doFilter(req,responseWrapper); 

      // Get character array representing output. 
      char[] responseChars = responseWrapper.toCharArray(); 
      // Make a writer that compresses data and puts 
      // it into a byte array. 
      ByteArrayOutputStream byteStream =
						new ByteArrayOutputStream();
						GZIPOutputStream zipOut =
						new GZIPOutputStream(byteStream);
						OutputStreamWriter tempOut =
						new OutputStreamWriter(zipOut); 

      // Compress original output and put it into byte array. 
      tempOut.write(responseChars); 

      // Gzip streams must be explicitly closed. 
      tempOut.close(); 

      // Update the Content-Length header. 
      res.setContentLength(byteStream.size()); 
      // Send compressed result to client. 
      OutputStream realOut = res.getOutputStream();
						byteStream.writeTo(realOut); 
    } 
  } 
  /** Store the FilterConfig object in case subclasses 
   *  want it. 
   */ 

  public void init(FilterConfig config) 
      throws ServletException {
    this.config = config; 
  } 

  protected FilterConfig getFilterConfig() {
    return(config); 
  } 

  public void destroy() {} 

  private boolean isGzipSupported(HttpServletRequest req) {
						String browserEncodings =
						req.getHeader("Accept-Encoding");
						return((browserEncodings != null) &&
						(browserEncodings.indexOf("gzip") != -1));
						} 
} 

Listing 9.21. LongServlet.java
package moreservlets; 

import java.io.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 

/** Servlet with <B>long</B> output. Used to test 
 *  the effect of the compression filter of Chapter 9. 
 */ 

public class LongServlet extends HttpServlet {
  public void doGet(HttpServletRequest request, 
                    HttpServletResponse response) 
      throws ServletException, IOException {
    response.setContentType("text/html"); 
    PrintWriter out = response.getWriter(); 
    String docType = 
      "<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 " + 
      "Transitional//EN">
"; 
    String title = "Long Page"; 
    out.println 
      (docType + 
       "<HTML>
" + 
       "<HEAD><TITLE>" + title + "</TITLE></HEAD>
" + 
       "<BODY BGCOLOR="#FDF5E6">
" + 
       "<H1 ALIGN="CENTER">" + title + "</H1>
"); 
    String line = "Blah, blah, blah, blah, blah. " + 
                  "Yadda, yadda, yadda, yadda."; 
    for(int i=0; i<10000; i++) {
      out.println(line); 
    } 
    out.println("</BODY></HTML>"); 
  } 
} 

Listing 9.22. web.xml (Excerpt for compression filter)
<?xml version="1.0" encoding="ISO-8859-1"?> 
<!DOCTYPE web-app PUBLIC 
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
    "http://java.sun.com/dtd/web-app_2_3.dtd"> 

<web-app> 
  <!-- ... --> 

  <!-- Register the name "CompressionFilter" for 
       moreservlets.filters.CompressionFilter. 
  --> 
  <filter> 
    <filter-name>CompressionFilter</filter-name> 
    <filter-class> 
      moreservlets.filters.CompressionFilter 
    </filter-class> 
  </filter> 
  <!-- ... --> 
  <!-- Apply CompressionFilter to the servlet named 
      "LongServlet". 
  --> 
  <filter-mapping> 
    <filter-name>CompressionFilter</filter-name> 
    <servlet-name>LongServlet</servlet-name> 
  </filter-mapping> 
  <!-- ... --> 

  <!-- Give a name to the servlet that generates long 
       (but very exciting!) output. 
  --> 
  <servlet> 
    <servlet-name>LongServlet</servlet-name> 
    <servlet-class>moreservlets.LongServlet</servlet-class> 
  </servlet> 
  <!-- ... --> 

  <!-- Make /LongServlet invoke the servlet 
       named LongServlet (i.e., moreservlets.LongServlet). 
  --> 
  <servlet-mapping> 
    <servlet-name>LongServlet</servlet-name> 
    <url-pattern>/LongServlet</url-pattern> 
  </servlet-mapping> 

  <!-- Turn off invoker. Send requests to index.jsp. --> 
  <servlet-mapping> 
    <servlet-name>Redirector</servlet-name> 
    <url-pattern>/servlet/*</url-pattern> 
  </servlet-mapping> 

  <!-- ... --> 
</web-app> 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.189.67