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