Commits

ltor...@81dbac14-341a-0410-aa85-cbcd92e6f43e  committed 49585fe

Support of GZip filters in the filter chain
Issue number: CACHE-155
Obtained from: Fernando Martins
Submitted by: Lars Torunski

  • Participants
  • Parent commits d0467bf

Comments (0)

Files changed (3)

File src/core/java/com/opensymphony/oscache/web/filter/CacheFilter.java

  * @version $Revision$
  */
 public class CacheFilter implements Filter {
-
-    private final Log log = LogFactory.getLog(this.getClass());
-
     // Header
     public static final String HEADER_LAST_MODIFIED = "Last-Modified";
     public static final String HEADER_CONTENT_TYPE = "Content-Type";
+    public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
     public static final String HEADER_EXPIRES = "Expires";
     public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
-    
+
     // Fragment parameter
     public static final int FRAGMENT_AUTODETECT = -1;
     public static final int FRAGMENT_NO = 0;
     public static final int FRAGMENT_YES = 1;
 
+    // request attribute to avoid reentrance
+    private final static String REQUEST_FILTERED = "__oscache_filtered";
+
+    // the policy for the expires header
+    private static final ExpiresRefreshPolicy EXPIRES_REFRESH_POLICY = new ExpiresRefreshPolicy();
+    private final Log log = LogFactory.getLog(this.getClass());
+
     // filter variables
     private FilterConfig config;
     private ServletCacheAdministrator admin = null;
     private int cacheScope = PageContext.APPLICATION_SCOPE; // filter scope - default is APPLICATION
+    private int fragment = FRAGMENT_AUTODETECT; // defines if this filter handles fragments of a page - default is auto detect
     private int time = 60 * 60; // time before cache should be refreshed - default one hour (in seconds)
-    private int fragment = FRAGMENT_AUTODETECT; // defines if this filter handles fragments of a page - default is auto detect
-
-    // request attribute to avoid reentrance
-    private final static String REQUEST_FILTERED = "__oscache_filtered";
-    // the policy for the expires header
-    private static final ExpiresRefreshPolicy EXPIRES_REFRESH_POLICY = new ExpiresRefreshPolicy();
 
     /**
      * Filter clean-up
             chain.doFilter(request, response);
             return;
         }
+
         request.setAttribute(REQUEST_FILTERED, Boolean.TRUE);
-        
+
         HttpServletRequest httpRequest = (HttpServletRequest) request;
 
         // checks if the response is a fragment of a apge
         boolean fragmentRequest = isFragment(httpRequest);
-        
+
         // generate the cache entry key
         String key = generateEntryKey(httpRequest);
-        
+
         // avoid useless session creation for application scope pages (CACHE-129)
         Cache cache;
+
         if (cacheScope == PageContext.SESSION_SCOPE) {
             cache = admin.getSessionScopeCache(httpRequest.getSession(true));
         } else {
             if (log.isInfoEnabled()) {
                 log.info("<cache>: Using cached entry for " + key);
             }
-            
-            // only reply with SC_NOT_MODIFIED
-            // if the client has already the newest page and the reponse isn't a fragment in a page 
+
             if (!fragmentRequest) {
                 long clientLastModified = httpRequest.getDateHeader(HEADER_IF_MODIFIED_SINCE); // will return -1 if no header...
 
+                // only reply with SC_NOT_MODIFIED
+                // if the client has already the newest page and the reponse isn't a fragment in a page 
                 if ((clientLastModified != -1) && (clientLastModified >= respContent.getLastModified())) {
                     ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                     return;
                 }
             }
 
-            respContent.writeTo(response, fragmentRequest);
-            
+            respContent.writeTo(response, fragmentRequest, acceptsGZipEncoding(httpRequest));
         } catch (NeedsRefreshException nre) {
             boolean updateSucceeded = false;
 
             }
         }
     }
-    
+
     /**
      * Initialize the filter. This retrieves a {@link ServletCacheAdministrator}
      * instance and configures the filter based on any initialization parameters.<p>
      * are <code>application</code> (default), <code>session</code>, <code>request</code> and
      * <code>page</code>.
      * <li><b>fragment</b> - defines if this filter handles fragments of a page. Acceptable values
-     * are <code>-1</code> (auto detect), <code>0</code> (false) and <code>1</code> (true). 
-     * The default value is auto detect.</li>     
-     * 
+     * are <code>-1</code> (auto detect), <code>0</code> (false) and <code>1</code> (true).
+     * The default value is auto detect.</li>
+     *
      * @param filterConfig The filter configuration
      */
     public void init(FilterConfig filterConfig) {
         //Get whatever settings we want...
         config = filterConfig;
         admin = ServletCacheAdministrator.getInstance(config.getServletContext());
-        
+
         //Will work this out later
         try {
             time = Integer.parseInt(config.getInitParameter("time"));
 
         try {
             fragment = Integer.parseInt(config.getInitParameter("fragment"));
+
             if ((fragment < FRAGMENT_AUTODETECT) || (fragment > FRAGMENT_YES)) {
                 log.info("Wrong init parameter 'fragment', setting to 'auto detect':" + fragment);
                 fragment = FRAGMENT_AUTODETECT;
         } catch (Exception e) {
             log.info("Could not get init parameter 'fragment', defaulting to 'auto detect'.");
         }
-        
     }
-    
+
     /**
      * Creates the cache key for the CacheFilter.
      *
 
     /**
      * Checks if the request is a fragment in a page.
-     * 
+     *
      * According to Java Servlet API 2.2 (8.2.1 Dispatching Requests, Included
      * Request Parameters), when a servlet is being used from within an include,
      * the attribute <code>javax.servlet.include.request_uri</code> is set.
-     * According to Java Servlet API 2.3 this is excepted for servlets obtained 
+     * According to Java Servlet API 2.3 this is excepted for servlets obtained
      * by using the getNamedDispatcher method.
-     * 
+     *
      * @param request the to be handled request
      * @return true if the request is a fragment in a page
      */
-    public boolean isFragment(HttpServletRequest request) {
+    protected boolean isFragment(HttpServletRequest request) {
         if (fragment == FRAGMENT_AUTODETECT) {
             return request.getAttribute("javax.servlet.include.request_uri") != null;
         } else {
-            return (fragment == FRAGMENT_NO) ? false : true; 
+            return (fragment == FRAGMENT_NO) ? false : true;
         }
     }
-    
+
     /**
-     * Checks if the request was filtered before, so 
+     * Checks if the request was filtered before, so
      * guarantees to be executed once per request. You
      * can override this methods to define a more specific
      * behaviour.
-     * 
+     *
      * @param request checks if the request was filtered before.
      * @return true if it is the first execution
      */
-    public boolean isFilteredBefore(ServletRequest request) {
+    protected boolean isFilteredBefore(ServletRequest request) {
         return request.getAttribute(REQUEST_FILTERED) != null;
     }
-    
+
+    /**
+     * Check if the client browser support gzip compression.
+     * @param request the http request
+     * @return true if client browser supports GZIP
+     */
+    protected boolean acceptsGZipEncoding(HttpServletRequest request) {
+        String acceptEncoding = request.getHeader("Accept-Encoding");
+        return  (acceptEncoding != null) && (acceptEncoding.indexOf("gzip") != -1);
+    }
 }

File src/core/java/com/opensymphony/oscache/web/filter/CacheHttpServletResponseWrapper.java

     private PrintWriter cachedWriter;
     private ResponseContent result = null;
     private SplitServletOutputStream cacheOut = null;
+    private boolean fragment = false;
     private int status = SC_OK;
-    private boolean fragment = false;
 
     /**
      * Constructor
         if (log.isDebugEnabled()) {
             log.debug("ContentType: " + value);
         }
+
         super.setContentType(value);
         result.setContentType(value);
     }
         if ((!fragment) && (CacheFilter.HEADER_LAST_MODIFIED.equalsIgnoreCase(name))) {
             result.setLastModified(value);
         }
-        
+
         // implement RFC 2616 14.21 Expires (without max-age)
         if (CacheFilter.HEADER_EXPIRES.equalsIgnoreCase(name)) {
             result.setExpires(value);
         if ((!fragment) && (CacheFilter.HEADER_LAST_MODIFIED.equalsIgnoreCase(name))) {
             result.setLastModified(value);
         }
-        
+
         // implement RFC 2616 14.21 Expires (without max-age)
         if (CacheFilter.HEADER_EXPIRES.equalsIgnoreCase(name)) {
             result.setExpires(value);
 
         if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
             result.setContentType(value);
-        }        
-        
+        }
+
+        if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
+            result.setContentEncoding(value);
+        }
+
         super.setHeader(name, value);
     }
 
 
         if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
             result.setContentType(value);
-        }        
-        
+        }
+
+        if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
+            result.setContentEncoding(value);
+        }
+
         super.addHeader(name, value);
     }
 
         super.setLocale(value);
         result.setLocale(value);
     }
-    
+
     /**
      * Get an output stream
      *

File src/core/java/com/opensymphony/oscache/web/filter/ResponseContent.java

 
 import java.io.*;
 
+import java.util.Enumeration;
 import java.util.Locale;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipException;
 
+import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 /**
 public class ResponseContent implements Serializable {
     private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
     private Locale locale = null;
+    private String contentEncoding = null;
     private String contentType = null;
     private byte[] content = null;
+    private long expires = Long.MAX_VALUE;
     private long lastModified = -1;
-    private long expires = Long.MAX_VALUE;
 
     /**
      * Set the content type. We capture this so that when we serve this
         lastModified = value;
     }
 
+    public void setContentEncoding(String contentEncoding) {
+        this.contentEncoding = contentEncoding;
+    }
+
     /**
      * Set the Locale. We capture this so that when we serve this data from
      * cache, we can set the correct locale on the response.
      * @throws IOException
      */
     public void writeTo(ServletResponse response) throws IOException {
-        writeTo(response, false);
+        writeTo(response, false, false);
     }
-    
+
     /**
      * Writes this cached data out to the supplied <code>ServletResponse</code>.
      *
      * @param response The servlet response to output the cached content to.
      * @param fragment is true if this content a fragment or part of a page
+     * @param acceptsGZip is true if client browser supports gzip compression
      * @throws IOException
      */
-    public void writeTo(ServletResponse response, boolean fragment) throws IOException {
+    public void writeTo(ServletResponse response, boolean fragment, boolean acceptsGZip) throws IOException {
         //Send the content type and data to this response
         if (contentType != null) {
             response.setContentType(contentType);
         }
+        
+        // Don't support gzip compression if the content is a fragment of a page
+        if (fragment) {
+            acceptsGZip = false;
+        }
 
         // Don't add in the Last-Modified header in a fragment of a page
         if ((!fragment) && (response instanceof HttpServletResponse)) {
             ((HttpServletResponse) response).setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, lastModified);
         }
 
-        response.setContentLength(content.length);
-
         if (locale != null) {
             response.setLocale(locale);
         }
 
         OutputStream out = new BufferedOutputStream(response.getOutputStream());
-        out.write(content);
+
+        if (isContentGZiped()) {
+            if (acceptsGZip) {
+                ((HttpServletResponse) response).addHeader("Content-Encoding", "gzip");
+                response.setContentLength(content.length);
+                out.write(content);
+            } else {
+                // client doesn't support, so we have to uncompress it
+                ByteArrayInputStream bais = new ByteArrayInputStream(content);
+                GZIPInputStream zis = new GZIPInputStream(bais);
+
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                int numBytesRead = 0;
+                byte[] tempBytes = new byte[4196];
+
+                while ((numBytesRead = zis.read(tempBytes, 0, tempBytes.length)) != -1) {
+                    baos.write(tempBytes, 0, numBytesRead);
+                }
+
+                byte[] result = baos.toByteArray();
+
+                response.setContentLength(result.length);
+                out.write(result);
+            }
+        } else {
+            // the content isn't compressed
+            // regardless if the client browser supports gzip we will just return the content
+            response.setContentLength(content.length);
+            out.write(content);
+        }
         out.flush();
     }
+    
+    
+    /**
+     * @return true if the content is GZIP compressed
+     */
+    public boolean isContentGZiped() {
+        return "gzip".equals(contentEncoding);
+    }
 }