Book Home Java Servlet Programming Search this book

6.2. Compressed Content

The java.util.zip package was introduced in JDK 1.1. This package contains classes that support reading and writing the GZIP and ZIP compression formats. Although these classes were added to support Java Archive (JAR) files, they also provide a convenient, standard way for a servlet to send compressed content.

Compressed content doesn't look any different to the end user because it's decompressed by the browser before it's displayed. Yet, while it looks the same, it can improve the end user's experience by reducing the time required to download the content from the server. For heavily compressable content such as HTML, compression can reduce transmission times by an order of magnitude. Quite a trick! Just bear in mind that to compress content dynamically forces the server to perform extra work, so any speed-up in transmission time has to be weighed against slower server performance.

By now you should be familiar with the idea that a servlet can send a Content-Type header as part of its response to tell the client the type of information being returned. To send compressed content, a servlet must also send a Content-Encoding header to tell the client the scheme by which the content has been encoded. Under the HTTP 1.0 specification, the possible encoding schemes are gzip (or x-gzip) and compress (or x-compress) for GZIP and ZIP compression formats, respectively.

Not all clients understand the gzip and compress encodings. To tell the server which encoding schemes it understands, a client may send an Accept-Encoding header that specifies acceptable encoding schemes as a comma-separated list. Most browsers do not yet provide this header--even those that do support compressed encodings. For now, a servlet has to decide that without the header it won't send compressed content, or it has to examine the User-Agent header to see if the browser is one that supports compression. Of the current popular browsers, only Netscape Navigator 3 and 4 on Unix and Microsoft Internet Explorer 4 on Windows support GZIP encoding, and none support ZIP encoding.

Although negotiating which compression format to use can involve a fair amount of logic, actually sending the compressed content could hardly be simpler. The servlet just wraps its standard ServletOutputStream with a GZIPOutputStream or ZipOutputStream . Be sure to call out.close() when your servlet is done writing output, so that the appropriate trailer for the compression format is written. Ah, the wonders of Java!

Example 6-11 shows the ViewFile servlet from Chapter 4, "Retrieving Information" rewritten to send compressed content whenever possible. We'd show you a screen shot, but there's nothing new to see. As we said before, an end user cannot tell that the server sent compressed content to the browser--except perhaps with reduced download times.

Example 6-11. Sending compressed content

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

import com.oreilly.servlet.ServletUtils;

public class ViewFileCompress extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res) 
                               throws ServletException, IOException {

    OutputStream out = null;

    // Select the appropriate content encoding based on the
    // client's Accept-Encoding header. Choose GZIP if the header 
    // includes "gzip". Choose ZIP if the header includes "compress". 
    // Choose no compression otherwise.
    String encodings = req.getHeader("Accept-Encoding");
    if (encodings != null && encodings.indexOf("gzip") != -1) {
      // Go with GZIP
      res.setHeader("Content-Encoding", "gzip");
      out = new GZIPOutputStream(res.getOutputStream());
    }
    else if (encodings != null && encodings.indexOf("compress") != -1) {
      // Go with ZIP
      res.setHeader("Content-Encoding", "x-compress");
      out = new ZipOutputStream(res.getOutputStream());
      ((ZipOutputStream)out).putNextEntry(new ZipEntry("dummy name"));
    }
    else {
      // No compression
      out = res.getOutputStream();
    }
    res.setHeader("Vary", "Accept-Encoding");

    // Get the file to view
    String file = req.getPathTranslated();

    // No file, nothing to view
    if (file == null) {
      res.sendError(res.SC_FORBIDDEN);
      return;
    }

    // Get and set the type of the file
    String contentType = getServletContext().getMimeType(file);
    res.setContentType(contentType);

    // Return the file
    try {
      ServletUtils.returnFile(file, out);
    }
    catch (FileNotFoundException e) { 
      res.sendError(res.SC_NOT_FOUND);
      return;
    }
    catch (IOException e) {
      getServletContext().log(e, "Problem sending file");
      res.sendError(res.SC_INTERNAL_SERVER_ERROR,
                    ServletUtils.getStackTraceAsString(e));
    }

    // Write the compression trailer and close the output stream
    out.close();
  }
}

The servlet begins by declaring a nullOutputStream and then setting this OutputStream to a GZIPOutputStream, ZipOutputStream, or ServletOutput-Stream, depending on the received Accept-Encoding header. As it selects which output stream to use, the servlet sets the Content-Encoding header accordingly. When sending compressed content, this header must be set for the client to run the appropriate decompression algorithm. The servlet also sets the Vary header to the value Accept-Encoding to be polite and indicate to the client that the servlet varies its output depending on the Accept-Encoding header. Most clients ignore this header.

After this early logic, the servlet can treat the output stream as just another OutputStream. It could wrap the stream with a PrintStream or PrintWriter, or it could pass it to a GifEncoder. But, no matter what it does, the servlet has to be sure to call out.close() when it's finished sending content. This call writes the appropriate trailer to the compressed stream.

There is some content that should not be compressed. For example, GIF and JPEG images are already compressed as part of their encoding, so there's no benefit in compressing them again. An improved version of the FileViewCompressed servlet would detect when it's returning an image and not bother with an attempt at further compression. Another improvement would be to rewrite this servlet as a filter--compressing whatever content is piped through it.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.