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