View Javadoc

1   // ========================================================================
2   // Copyright (c) 2007-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  package org.eclipse.jetty.servlets;
14  
15  import java.io.IOException;
16  import java.io.OutputStream;
17  import java.io.OutputStreamWriter;
18  import java.io.PrintWriter;
19  import java.io.UnsupportedEncodingException;
20  import java.util.HashSet;
21  import java.util.Set;
22  import java.util.StringTokenizer;
23  
24  import javax.servlet.FilterChain;
25  import javax.servlet.FilterConfig;
26  import javax.servlet.ServletException;
27  import javax.servlet.ServletRequest;
28  import javax.servlet.ServletResponse;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.eclipse.jetty.continuation.Continuation;
33  import org.eclipse.jetty.continuation.ContinuationListener;
34  import org.eclipse.jetty.continuation.ContinuationSupport;
35  import org.eclipse.jetty.http.HttpMethods;
36  import org.eclipse.jetty.http.gzip.GzipResponseWrapper;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  
40  /* ------------------------------------------------------------ */
41  /** GZIP Filter
42   * This filter will gzip the content of a response iff: <ul>
43   * <li>The filter is mapped to a matching path</li>
44   * <li>The response status code is >=200 and <300
45   * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
46   * <li>The content-type is in the comma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or
47   * if no mimeTypes are defined the content-type is not "application/gzip"</li>
48   * <li>No content-encoding is specified by the resource</li>
49   * </ul>
50   * 
51   * <p>
52   * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and
53   * CPU cycles.   If this filter is mapped for static content, then use of efficient direct NIO may be 
54   * prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is 
55   * advised instead.
56   * </p>
57   * <p>
58   * This filter extends {@link UserAgentFilter} and if the the initParameter <code>excludedAgents</code> 
59   * is set to a comma separated list of user agents, then these agents will be excluded from gzip content.
60   * </p>
61   *
62   */
63  public class GzipFilter extends UserAgentFilter
64  {
65      private static final Logger LOG = Log.getLogger(GzipFilter.class);
66  
67      protected Set<String> _mimeTypes;
68      protected int _bufferSize=8192;
69      protected int _minGzipSize=256;
70      protected Set<String> _excluded;
71      
72      /* ------------------------------------------------------------ */
73      /**
74       * @see org.eclipse.jetty.servlets.UserAgentFilter#init(javax.servlet.FilterConfig)
75       */
76      public void init(FilterConfig filterConfig) throws ServletException
77      {
78          super.init(filterConfig);
79          
80          String tmp=filterConfig.getInitParameter("bufferSize");
81          if (tmp!=null)
82              _bufferSize=Integer.parseInt(tmp);
83  
84          tmp=filterConfig.getInitParameter("minGzipSize");
85          if (tmp!=null)
86              _minGzipSize=Integer.parseInt(tmp);
87          
88          tmp=filterConfig.getInitParameter("mimeTypes");
89          if (tmp!=null)
90          {
91              _mimeTypes=new HashSet<String>();
92              StringTokenizer tok = new StringTokenizer(tmp,",",false);
93              while (tok.hasMoreTokens())
94                  _mimeTypes.add(tok.nextToken());
95          }
96          
97          tmp=filterConfig.getInitParameter("excludedAgents");
98          if (tmp!=null)
99          {
100             _excluded=new HashSet<String>();
101             StringTokenizer tok = new StringTokenizer(tmp,",",false);
102             while (tok.hasMoreTokens())
103                 _excluded.add(tok.nextToken());
104         }
105     }
106 
107     /* ------------------------------------------------------------ */
108     /**
109      * @see org.eclipse.jetty.servlets.UserAgentFilter#destroy()
110      */
111     public void destroy()
112     {
113     }
114 
115     /* ------------------------------------------------------------ */
116     /**
117      * @see org.eclipse.jetty.servlets.UserAgentFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
118      */
119     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
120         throws IOException, ServletException
121     {
122         HttpServletRequest request=(HttpServletRequest)req;
123         HttpServletResponse response=(HttpServletResponse)res;
124 
125         String ae = request.getHeader("accept-encoding");
126         if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")
127                 && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod()))
128         {
129             if (_excluded!=null)
130             {
131                 String ua=getUserAgent(request);
132                 if (_excluded.contains(ua))
133                 {
134                     super.doFilter(request,response,chain);
135                     return;
136                 }
137             }
138 
139             final GzipResponseWrapper wrappedResponse=newGzipResponseWrapper(request,response);
140             
141             boolean exceptional=true;
142             try
143             {
144                 super.doFilter(request,wrappedResponse,chain);
145                 exceptional=false;
146             }
147             finally
148             {
149                 Continuation continuation = ContinuationSupport.getContinuation(request);
150                 if (continuation.isSuspended() && continuation.isResponseWrapped())   
151                 {
152                     continuation.addContinuationListener(new ContinuationListener()
153                     {
154                         public void onComplete(Continuation continuation)
155                         {
156                             try
157                             {
158                                 wrappedResponse.finish();
159                             }
160                             catch(IOException e)
161                             {
162                                 LOG.warn(e);
163                             }
164                         }
165 
166                         public void onTimeout(Continuation continuation)
167                         {}
168                     });
169                 }
170                 else if (exceptional && !response.isCommitted())
171                 {
172                     wrappedResponse.resetBuffer();
173                     wrappedResponse.noGzip();
174                 }
175                 else
176                     wrappedResponse.finish();
177             }
178         }
179         else
180         {
181             super.doFilter(request,response,chain);
182         }
183     }
184     
185     /**
186      * Allows derived implementations to replace ResponseWrapper implementation.
187      *
188      * @param request the request
189      * @param response the response
190      * @return the gzip response wrapper
191      */
192     protected GzipResponseWrapper newGzipResponseWrapper(HttpServletRequest request, HttpServletResponse response)
193     {
194         return new GzipResponseWrapper(request, response)
195         {
196             {
197                 setMimeTypes(GzipFilter.this._mimeTypes);
198                 setBufferSize(GzipFilter.this._bufferSize);
199                 setMinGzipSize(GzipFilter.this._minGzipSize);
200             }
201             
202             @Override
203             protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
204             {
205                 return GzipFilter.this.newWriter(out,encoding);
206             }
207         };
208     }
209     
210     /**
211      * Allows derived implementations to replace PrintWriter implementation.
212      *
213      * @param out the out
214      * @param encoding the encoding
215      * @return the prints the writer
216      * @throws UnsupportedEncodingException
217      */
218     protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
219     {
220         return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
221     }
222 }