Open Source Repository

Home /jodd/jodd-3.3.2 | Repository Home



jodd/servlet/filter/GzipFilter.java
// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.

package jodd.servlet.filter;

import java.io.IOException;
import java.util.StringTokenizer;

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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Compresses output with gzip, for browsers that supports it.<p>
 *
 * Configuration of this filter is based on the following initialization
 * parameters:
 *
 <ul>
 <li><strong>threshold</strong> - min number of bytes for compressing
 * or 0 for no compression at all.</li>
 *
 <li><strong>match</strong> - comma separated string patterns to be found
 * in uri for using gzip. Only uri's that have these patterns inside will use
 * gzip.</li>
 *
 <li><strong>excludes</strong> - comma separated string patterns to be excluded
 * if found in uri for using gzip. It is applied only on matched uris.</li>
 *
 </ul>
 *
 <p>
 * This filter has been build and extend from Tomcats example.
 <p>
 *
 * Technical notes: i have found that this is the only way how gzip filter
 * can be build. The first idea that comes to mind is to wrap response to
 * some character-based wrapper, and then to gzip it to the output. This
 * works fine except when forwarding is used: forwarded page is gzipped, but
 * response header is no more there! I have not an idea how to fix this
 * except from this approach presented in Tomcat.
 */
public class GzipFilter implements Filter {
  
  private FilterConfig config;

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

  /**
   * If browser supports 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.
   *
   * If browser does not support gzip, invoke resource normally.
   *
   @exception ServletException
   @exception IOException
   */
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chainthrows ServletException, IOException {
    HttpServletRequest req = (HttpServletRequestrequest;
    HttpServletResponse res = (HttpServletResponseresponse;
    if ( (threshold == 0|| (isGzipSupported(req== false|| (isGzipEligible(req== false) ) {
      chain.doFilter(request, response);
      return;
    }
    GzipResponseWrapper wrappedResponse = new GzipResponseWrapper(res);
    wrappedResponse.setCompressionThreshold(threshold);
    try {
      chain.doFilter(request, wrappedResponse);
    finally {
      wrappedResponse.finishResponse();
    }
  }

  /**
   * Comma separated string patterns to be found in the request URI
   */
  protected String uriMatch;
  
  /**
   * Comma separated string patterns to be excluded in the request URI if
   * founded by match.
   */
  protected String uriExclude;

  /**
   * Minimal threshold.
   */
  private int minThreshold = 128;

  /**
   * The threshold number to compress, (0 == no compression).
   */
    protected int threshold;

  private String[] extensions;
  private String[] excludes;

  /**
   * Filter initialization.
   *
   @exception ServletException
   */
  public void init(FilterConfig configthrows ServletException {
    this.config = config;
    extensions = null;

    // min size
    try {
      threshold = Integer.parseInt(config.getInitParameter("threshold"));
    catch (NumberFormatException nfe) {
      threshold = 0;
    }
    if (threshold < minThreshold) {
      threshold = 0;
    }

    // match string
    uriMatch = config.getInitParameter("match");
    if ((uriMatch != null&& (uriMatch.equals("*"== false)) {
      StringTokenizer st = new StringTokenizer(uriMatch, ",");
      int i = st.countTokens();
      if (i >= 1) {
        extensions = new String[i];
        i = 0;
        while (st.hasMoreTokens()) {
          extensions[i= st.nextToken().trim();
          i++;
        }
      }
    }
    // exclude string
    uriExclude = config.getInitParameter("exclude");
    if (uriExclude != null) {
      StringTokenizer st = new StringTokenizer(uriExclude, ",");
      int i = st.countTokens();
      if (i >= 1) {
        excludes = new String[i];
        i = 0;
        while (st.hasMoreTokens()) {
          excludes[i= st.nextToken().trim();
          i++;
        }
      }
    }
  }

  public void destroy() {
    config = null;
    extensions = null;
    excludes = null;
  }

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

  /**
   * Determine if uri is eligible for gzip-ing.
   *
   @return true to gzip the uri, otherwise false
   */
  private boolean isGzipEligible(HttpServletRequest req) {
    String uri = req.getRequestURI();
    if (uri == null) {
      return false;
    }
    boolean result = false;
    
    if (extensions == null) {            // match=*
      result = true;
    else {
      for (String extension : extensions) {
        if (uri.indexOf(extension!= -1) {
          result = true;            // extension founded
          break;
        }
      }
    }

    if ((result == true&& (excludes != null)) {
      for (String exclude : excludes) {
        if (uri.indexOf(exclude!= -1) {
          result = false;            // excludes founded
          break;
        }
      }
    }
    return result;
  }
}