View Javadoc

1   // ========================================================================
2   // Copyright (c) Webtide LLC
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  
14  package org.eclipse.jetty.server.handler;
15  
16  import java.io.IOException;
17  import java.io.OutputStream;
18  import java.io.OutputStreamWriter;
19  import java.io.PrintWriter;
20  import java.io.UnsupportedEncodingException;
21  import java.util.HashSet;
22  import java.util.Set;
23  import java.util.StringTokenizer;
24  
25  import javax.servlet.ServletException;
26  import javax.servlet.http.HttpServletRequest;
27  import javax.servlet.http.HttpServletResponse;
28  
29  import org.eclipse.jetty.continuation.Continuation;
30  import org.eclipse.jetty.continuation.ContinuationListener;
31  import org.eclipse.jetty.continuation.ContinuationSupport;
32  import org.eclipse.jetty.http.HttpMethods;
33  import org.eclipse.jetty.http.gzip.GzipResponseWrapper;
34  import org.eclipse.jetty.server.Request;
35  import org.eclipse.jetty.util.log.Log;
36  
37  /* ------------------------------------------------------------ */
38  /**
39   * GZIP Handler This handler will gzip the content of a response if:
40   * <ul>
41   * <li>The filter is mapped to a matching path</li>
42   * <li>The response status code is >=200 and <300
43   * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
44   * <li>The content-type is in the comma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or if no mimeTypes are defined the
45   * content-type is not "application/gzip"</li>
46   * <li>No content-encoding is specified by the resource</li>
47   * </ul>
48   * 
49   * <p>
50   * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
51   * then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead.
52   * </p>
53   */
54  public class GzipHandler extends HandlerWrapper
55  {
56      protected Set<String> _mimeTypes;
57      protected Set<String> _excluded;
58      protected int _bufferSize = 8192;
59      protected int _minGzipSize = 256;
60  
61      /* ------------------------------------------------------------ */
62      /**
63       * Instantiates a new gzip handler.
64       */
65      public GzipHandler()
66      {
67      }
68  
69      /* ------------------------------------------------------------ */
70      /**
71       * Get the mime types.
72       * 
73       * @return mime types to set
74       */
75      public Set<String> getMimeTypes()
76      {
77          return _mimeTypes;
78      }
79  
80      /* ------------------------------------------------------------ */
81      /**
82       * Set the mime types.
83       * 
84       * @param mimeTypes
85       *            the mime types to set
86       */
87      public void setMimeTypes(Set<String> mimeTypes)
88      {
89          _mimeTypes = mimeTypes;
90      }
91  
92      /* ------------------------------------------------------------ */
93      /**
94       * Set the mime types.
95       * 
96       * @param mimeTypes
97       *            the mime types to set
98       */
99      public void setMimeTypes(String mimeTypes)
100     {
101         if (mimeTypes != null)
102         {
103             _mimeTypes = new HashSet<String>();
104             StringTokenizer tok = new StringTokenizer(mimeTypes,",",false);
105             while (tok.hasMoreTokens())
106             {
107                 _mimeTypes.add(tok.nextToken());
108             }
109         }
110     }
111 
112     /* ------------------------------------------------------------ */
113     /**
114      * Get the excluded user agents.
115      * 
116      * @return excluded user agents
117      */
118     public Set<String> getExcluded()
119     {
120         return _excluded;
121     }
122 
123     /* ------------------------------------------------------------ */
124     /**
125      * Set the excluded user agents.
126      * 
127      * @param excluded
128      *            excluded user agents to set
129      */
130     public void setExcluded(Set<String> excluded)
131     {
132         _excluded = excluded;
133     }
134 
135     /* ------------------------------------------------------------ */
136     /**
137      * Set the excluded user agents.
138      * 
139      * @param excluded
140      *            excluded user agents to set
141      */
142     public void setExcluded(String excluded)
143     {
144         if (excluded != null)
145         {
146             _excluded = new HashSet<String>();
147             StringTokenizer tok = new StringTokenizer(excluded,",",false);
148             while (tok.hasMoreTokens())
149                 _excluded.add(tok.nextToken());
150         }
151     }
152 
153     /* ------------------------------------------------------------ */
154     /**
155      * Get the buffer size.
156      * 
157      * @return the buffer size
158      */
159     public int setBufferSize()
160     {
161         return _bufferSize;
162     }
163 
164     /* ------------------------------------------------------------ */
165     /**
166      * Set the buffer size.
167      * 
168      * @param bufferSize
169      *            buffer size to set
170      */
171     public void setBufferSize(int bufferSize)
172     {
173         _bufferSize = bufferSize;
174     }
175 
176     /* ------------------------------------------------------------ */
177     /**
178      * Get the minimum reponse size.
179      * 
180      * @return minimum reponse size
181      */
182     public int getMinGzipSize()
183     {
184         return _minGzipSize;
185     }
186 
187     /* ------------------------------------------------------------ */
188     /**
189      * Set the minimum reponse size.
190      * 
191      * @param minGzipSize
192      *            minimum reponse size
193      */
194     public void setMinGzipSize(int minGzipSize)
195     {
196         _minGzipSize = minGzipSize;
197     }
198 
199     /* ------------------------------------------------------------ */
200     /**
201      * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
202      */
203     @Override
204     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
205     {
206         if (_handler!=null && isStarted())
207         {
208             String ae = request.getHeader("accept-encoding");
209             if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")
210                     && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod()))
211             {
212                 if (_excluded!=null)
213                 {
214                     String ua = request.getHeader("User-Agent");
215                     if (_excluded.contains(ua))
216                     {
217                         _handler.handle(target,baseRequest, request, response);
218                         return;
219                     }
220                 }
221 
222                 final GzipResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response);
223                 
224                 boolean exceptional=true;
225                 try
226                 {
227                     _handler.handle(target, baseRequest, request, wrappedResponse);
228                     exceptional=false;
229                 }
230                 finally
231                 {
232                     Continuation continuation = ContinuationSupport.getContinuation(request);
233                     if (continuation.isSuspended() && continuation.isResponseWrapped())   
234                     {
235                         continuation.addContinuationListener(new ContinuationListener()
236                         {
237                             public void onComplete(Continuation continuation)
238                             {
239                                 try
240                                 {
241                                     wrappedResponse.finish();
242                                 }
243                                 catch(IOException e)
244                                 {
245                                     Log.warn(e);
246                                 }
247                             }
248 
249                             public void onTimeout(Continuation continuation)
250                             {}
251                         });
252                     }
253                     else if (exceptional && !response.isCommitted())
254                     {
255                         wrappedResponse.resetBuffer();
256                         wrappedResponse.noGzip();
257                     }
258                     else
259                         wrappedResponse.finish();
260                 }
261             }
262             else
263             {
264                 _handler.handle(target,baseRequest, request, response);
265             }
266         }
267     }
268 
269     /**
270      * Allows derived implementations to replace ResponseWrapper implementation.
271      *
272      * @param request the request
273      * @param response the response
274      * @return the gzip response wrapper
275      */
276     protected GzipResponseWrapper newGzipResponseWrapper(HttpServletRequest request, HttpServletResponse response)
277     {
278         return new GzipResponseWrapper(request, response)
279         {
280             {
281                 setMimeTypes(GzipHandler.this._mimeTypes);
282                 setBufferSize(GzipHandler.this._bufferSize);
283                 setMinGzipSize(GzipHandler.this._minGzipSize);
284             }
285             
286             @Override
287             protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
288             {
289                 return GzipHandler.this.newWriter(out,encoding);
290             }
291         };
292     }
293     
294     /**
295      * Allows derived implementations to replace PrintWriter implementation.
296      *
297      * @param out the out
298      * @param encoding the encoding
299      * @return the prints the writer
300      * @throws UnsupportedEncodingException
301      */
302     protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
303     {
304         return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
305     }
306 }