All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.servlets.GzipFilter Maven / Gradle / Ivy

There is a newer version: 11.0.0.beta1
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.servlets;

import java.io.IOException;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationListener;
import org.eclipse.jetty.continuation.ContinuationSupport;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/* ------------------------------------------------------------ */
/** GZIP Filter
 * This filter will gzip or deflate the content of a response if: 
    *
  • The filter is mapped to a matching path
  • *
  • accept-encoding header is set to either gzip, deflate or a combination of those
  • *
  • The response status code is >=200 and <300 *
  • The content length is unknown or more than the minGzipSize initParameter or the minGzipSize is 0(default)
  • *
  • The content-type is in the comma separated list of mimeTypes set in the mimeTypes initParameter or * if no mimeTypes are defined the content-type is not "application/gzip"
  • *
  • No content-encoding is specified by the resource
  • *
* *

* If both gzip and deflate are specified in the accept-encoding header, then gzip will be used. *

*

* Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and * CPU cycles. If this filter is mapped for static content, then use of efficient direct NIO may be * prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is * advised instead. *

*

* This filter extends {@link UserAgentFilter} and if the the initParameter excludedAgents * is set to a comma separated list of user agents, then these agents will be excluded from gzip content. *

*

Init Parameters:

*
 * bufferSize                 The output buffer size. Defaults to 8192. Be careful as values <= 0 will lead to an 
 *                            {@link IllegalArgumentException}. 
 *                            See: {@link java.util.zip.GZIPOutputStream#GZIPOutputStream(java.io.OutputStream, int)}
 *                            and: {@link java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream, Deflater, int)}
 *                      
 * minGzipSize                Content will only be compressed if content length is either unknown or greater
 *                            than minGzipSize.
 *                      
 * deflateCompressionLevel    The compression level used for deflate compression. (0-9).
 *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
 *                            
 * deflateNoWrap              The noWrap setting for deflate compression. Defaults to true. (true/false)
 *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
 *
 * mimeTypes                  Comma separated list of mime types to compress. See description above.
 * 
 * excludedAgents             Comma separated list of user agents to exclude from compression. Does a 
 *                            {@link String#contains(CharSequence)} to check if the excluded agent occurs
 *                            in the user-agent header. If it does -> no compression
 *                            
 * excludeAgentPatterns       Same as excludedAgents, but accepts regex patterns for more complex matching.
 * 
 * excludePaths               Comma separated list of paths to exclude from compression. 
 *                            Does a {@link String#startsWith(String)} comparison to check if the path matches.
 *                            If it does match -> no compression. To match subpaths use excludePathPatterns
 *                            instead.
 * 
 * excludePathPatterns        Same as excludePath, but accepts regex patterns for more complex matching.
 * 
 * vary                       Set to the value of the Vary header sent with responses that could be compressed.  By default it is 
 *                            set to 'Vary: Accept-Encoding, User-Agent' since IE6 is excluded by default from the excludedAgents. 
 *                            If user-agents are not to be excluded, then this can be set to 'Vary: Accept-Encoding'.  Note also 
 *                            that shared caches may cache copies of a resource that is varied by User-Agent - one per variation of 
 *                            the User-Agent, unless the cache does some normalization of the UA string.
 * 
*/ public class GzipFilter extends UserAgentFilter { private static final Logger LOG = Log.getLogger(GzipFilter.class); public final static String GZIP="gzip"; public final static String ETAG_GZIP="--gzip\""; public final static String DEFLATE="deflate"; public final static String ETAG_DEFLATE="--deflate\""; public final static String ETAG="o.e.j.s.GzipFilter.ETag"; protected ServletContext _context; protected Set _mimeTypes; protected int _bufferSize=8192; protected int _minGzipSize=256; protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION; protected boolean _deflateNoWrap = true; protected Set _excludedAgents; protected Set _excludedAgentPatterns; protected Set _excludedPaths; protected Set _excludedPathPatterns; protected String _vary="Accept-Encoding, User-Agent"; private static final int STATE_SEPARATOR = 0; private static final int STATE_Q = 1; private static final int STATE_QVALUE = 2; private static final int STATE_DEFAULT = 3; /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.servlets.UserAgentFilter#init(javax.servlet.FilterConfig) */ @Override public void init(FilterConfig filterConfig) throws ServletException { super.init(filterConfig); _context=filterConfig.getServletContext(); String tmp=filterConfig.getInitParameter("bufferSize"); if (tmp!=null) _bufferSize=Integer.parseInt(tmp); tmp=filterConfig.getInitParameter("minGzipSize"); if (tmp!=null) _minGzipSize=Integer.parseInt(tmp); tmp=filterConfig.getInitParameter("deflateCompressionLevel"); if (tmp!=null) _deflateCompressionLevel=Integer.parseInt(tmp); tmp=filterConfig.getInitParameter("deflateNoWrap"); if (tmp!=null) _deflateNoWrap=Boolean.parseBoolean(tmp); tmp=filterConfig.getInitParameter("mimeTypes"); if (tmp!=null) { _mimeTypes=new HashSet(); StringTokenizer tok = new StringTokenizer(tmp,",",false); while (tok.hasMoreTokens()) _mimeTypes.add(tok.nextToken()); } tmp=filterConfig.getInitParameter("excludedAgents"); if (tmp!=null) { _excludedAgents=new HashSet(); StringTokenizer tok = new StringTokenizer(tmp,",",false); while (tok.hasMoreTokens()) _excludedAgents.add(tok.nextToken()); } tmp=filterConfig.getInitParameter("excludeAgentPatterns"); if (tmp!=null) { _excludedAgentPatterns=new HashSet(); StringTokenizer tok = new StringTokenizer(tmp,",",false); while (tok.hasMoreTokens()) _excludedAgentPatterns.add(Pattern.compile(tok.nextToken())); } tmp=filterConfig.getInitParameter("excludePaths"); if (tmp!=null) { _excludedPaths=new HashSet(); StringTokenizer tok = new StringTokenizer(tmp,",",false); while (tok.hasMoreTokens()) _excludedPaths.add(tok.nextToken()); } tmp=filterConfig.getInitParameter("excludePathPatterns"); if (tmp!=null) { _excludedPathPatterns=new HashSet(); StringTokenizer tok = new StringTokenizer(tmp,",",false); while (tok.hasMoreTokens()) _excludedPathPatterns.add(Pattern.compile(tok.nextToken())); } tmp=filterConfig.getInitParameter("vary"); if (tmp!=null) _vary=tmp; } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.servlets.UserAgentFilter#destroy() */ @Override public void destroy() { } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.servlets.UserAgentFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) */ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request=(HttpServletRequest)req; HttpServletResponse response=(HttpServletResponse)res; // If not a GET or an Excluded URI - no Vary because no matter what client, this URI is always excluded String requestURI = request.getRequestURI(); if (!HttpMethods.GET.equalsIgnoreCase(request.getMethod()) || isExcludedPath(requestURI)) { super.doFilter(request,response,chain); return; } // Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded if (_mimeTypes!=null && _mimeTypes.size()>0) { String mimeType = _context.getMimeType(request.getRequestURI()); if (mimeType!=null && !_mimeTypes.contains(mimeType)) { // handle normally without setting vary header super.doFilter(request,response,chain); return; } } // Excluded User-Agents String ua = getUserAgent(request); boolean ua_excluded=ua!=null&&isExcludedAgent(ua); // Acceptable compression type String compressionType = ua_excluded?null:selectCompression(request.getHeader("accept-encoding")); // Special handling for etags String etag = request.getHeader("If-None-Match"); if (etag!=null) { int dd=etag.indexOf("--"); if (dd>0) request.setAttribute(ETAG,etag.substring(0,dd)+(etag.endsWith("\"")?"\"":"")); } CompressedResponseWrapper wrappedResponse = createWrappedResponse(request,response,compressionType); boolean exceptional=true; try { super.doFilter(request,wrappedResponse,chain); exceptional=false; } finally { Continuation continuation = ContinuationSupport.getContinuation(request); if (continuation.isSuspended() && continuation.isResponseWrapped()) { continuation.addContinuationListener(new ContinuationListenerWaitingForWrappedResponseToFinish(wrappedResponse)); } else if (exceptional && !response.isCommitted()) { wrappedResponse.resetBuffer(); wrappedResponse.noCompression(); } else wrappedResponse.finish(); } } /* ------------------------------------------------------------ */ private String selectCompression(String encodingHeader) { // TODO, this could be a little more robust. // prefer gzip over deflate String compression = null; if (encodingHeader!=null) { String[] encodings = getEncodings(encodingHeader); if (encodings != null) { for (int i=0; i< encodings.length; i++) { if (encodings[i].toLowerCase(Locale.ENGLISH).contains(GZIP)) { if (isEncodingAcceptable(encodings[i])) { compression = GZIP; break; //prefer Gzip over deflate } } if (encodings[i].toLowerCase(Locale.ENGLISH).contains(DEFLATE)) { if (isEncodingAcceptable(encodings[i])) { compression = DEFLATE; //Keep checking in case gzip is acceptable } } } } } return compression; } private String[] getEncodings (String encodingHeader) { if (encodingHeader == null) return null; return encodingHeader.split(","); } private boolean isEncodingAcceptable(String encoding) { int state = STATE_DEFAULT; int qvalueIdx = -1; for (int i=0;i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy