View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.handler;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.OutputStreamWriter;
24  import java.io.PrintWriter;
25  import java.io.UnsupportedEncodingException;
26  import java.util.HashSet;
27  import java.util.Set;
28  import java.util.StringTokenizer;
29  import java.util.zip.DeflaterOutputStream;
30  import java.util.zip.GZIPOutputStream;
31  
32  import javax.servlet.ServletException;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  
36  import org.eclipse.jetty.continuation.Continuation;
37  import org.eclipse.jetty.continuation.ContinuationListener;
38  import org.eclipse.jetty.continuation.ContinuationSupport;
39  import org.eclipse.jetty.http.HttpMethods;
40  import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
41  import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
42  import org.eclipse.jetty.server.Request;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  
46  /* ------------------------------------------------------------ */
47  /**
48   * GZIP Handler This handler will gzip the content of a response if:
49   * <ul>
50   * <li>The filter is mapped to a matching path</li>
51   * <li>The response status code is >=200 and <300
52   * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
53   * <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
54   * content-type is not "application/gzip"</li>
55   * <li>No content-encoding is specified by the resource</li>
56   * </ul>
57   * 
58   * <p>
59   * 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,
60   * 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.
61   * </p>
62   */
63  public class GzipHandler extends HandlerWrapper
64  {
65      private static final Logger LOG = Log.getLogger(GzipHandler.class);
66  
67      protected Set<String> _mimeTypes;
68      protected Set<String> _excluded;
69      protected int _bufferSize = 8192;
70      protected int _minGzipSize = 256;
71  
72      /* ------------------------------------------------------------ */
73      /**
74       * Instantiates a new gzip handler.
75       */
76      public GzipHandler()
77      {
78      }
79  
80      /* ------------------------------------------------------------ */
81      /**
82       * Get the mime types.
83       * 
84       * @return mime types to set
85       */
86      public Set<String> getMimeTypes()
87      {
88          return _mimeTypes;
89      }
90  
91      /* ------------------------------------------------------------ */
92      /**
93       * Set the mime types.
94       * 
95       * @param mimeTypes
96       *            the mime types to set
97       */
98      public void setMimeTypes(Set<String> mimeTypes)
99      {
100         _mimeTypes = mimeTypes;
101     }
102 
103     /* ------------------------------------------------------------ */
104     /**
105      * Set the mime types.
106      * 
107      * @param mimeTypes
108      *            the mime types to set
109      */
110     public void setMimeTypes(String mimeTypes)
111     {
112         if (mimeTypes != null)
113         {
114             _mimeTypes = new HashSet<String>();
115             StringTokenizer tok = new StringTokenizer(mimeTypes,",",false);
116             while (tok.hasMoreTokens())
117             {
118                 _mimeTypes.add(tok.nextToken());
119             }
120         }
121     }
122 
123     /* ------------------------------------------------------------ */
124     /**
125      * Get the excluded user agents.
126      * 
127      * @return excluded user agents
128      */
129     public Set<String> getExcluded()
130     {
131         return _excluded;
132     }
133 
134     /* ------------------------------------------------------------ */
135     /**
136      * Set the excluded user agents.
137      * 
138      * @param excluded
139      *            excluded user agents to set
140      */
141     public void setExcluded(Set<String> excluded)
142     {
143         _excluded = excluded;
144     }
145 
146     /* ------------------------------------------------------------ */
147     /**
148      * Set the excluded user agents.
149      * 
150      * @param excluded
151      *            excluded user agents to set
152      */
153     public void setExcluded(String excluded)
154     {
155         if (excluded != null)
156         {
157             _excluded = new HashSet<String>();
158             StringTokenizer tok = new StringTokenizer(excluded,",",false);
159             while (tok.hasMoreTokens())
160                 _excluded.add(tok.nextToken());
161         }
162     }
163 
164     /* ------------------------------------------------------------ */
165     /**
166      * Get the buffer size.
167      * 
168      * @return the buffer size
169      */
170     public int getBufferSize()
171     {
172         return _bufferSize;
173     }
174 
175     /* ------------------------------------------------------------ */
176     /**
177      * Set the buffer size.
178      * 
179      * @param bufferSize
180      *            buffer size to set
181      */
182     public void setBufferSize(int bufferSize)
183     {
184         _bufferSize = bufferSize;
185     }
186 
187     /* ------------------------------------------------------------ */
188     /**
189      * Get the minimum reponse size.
190      * 
191      * @return minimum reponse size
192      */
193     public int getMinGzipSize()
194     {
195         return _minGzipSize;
196     }
197 
198     /* ------------------------------------------------------------ */
199     /**
200      * Set the minimum reponse size.
201      * 
202      * @param minGzipSize
203      *            minimum reponse size
204      */
205     public void setMinGzipSize(int minGzipSize)
206     {
207         _minGzipSize = minGzipSize;
208     }
209 
210     /* ------------------------------------------------------------ */
211     /**
212      * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
213      */
214     @Override
215     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
216     {
217         if (_handler!=null && isStarted())
218         {
219             String ae = request.getHeader("accept-encoding");
220             if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")
221                     && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod()))
222             {
223                 if (_excluded!=null)
224                 {
225                     String ua = request.getHeader("User-Agent");
226                     if (_excluded.contains(ua))
227                     {
228                         _handler.handle(target,baseRequest, request, response);
229                         return;
230                     }
231                 }
232 
233                 final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response);
234                 
235                 boolean exceptional=true;
236                 try
237                 {
238                     _handler.handle(target, baseRequest, request, wrappedResponse);
239                     exceptional=false;
240                 }
241                 finally
242                 {
243                     Continuation continuation = ContinuationSupport.getContinuation(request);
244                     if (continuation.isSuspended() && continuation.isResponseWrapped())   
245                     {
246                         continuation.addContinuationListener(new ContinuationListener()
247                         {
248                             public void onComplete(Continuation continuation)
249                             {
250                                 try
251                                 {
252                                     wrappedResponse.finish();
253                                 }
254                                 catch(IOException e)
255                                 {
256                                     LOG.warn(e);
257                                 }
258                             }
259 
260                             public void onTimeout(Continuation continuation)
261                             {}
262                         });
263                     }
264                     else if (exceptional && !response.isCommitted())
265                     {
266                         wrappedResponse.resetBuffer();
267                         wrappedResponse.noCompression();
268                     }
269                     else
270                         wrappedResponse.finish();
271                 }
272             }
273             else
274             {
275                 _handler.handle(target,baseRequest, request, response);
276             }
277         }
278     }
279 
280     /**
281      * Allows derived implementations to replace ResponseWrapper implementation.
282      *
283      * @param request the request
284      * @param response the response
285      * @return the gzip response wrapper
286      */
287     protected CompressedResponseWrapper newGzipResponseWrapper(HttpServletRequest request, HttpServletResponse response)
288     {
289         return new CompressedResponseWrapper(request,response)
290         {
291             {
292                 super.setMimeTypes(GzipHandler.this._mimeTypes);
293                 super.setBufferSize(GzipHandler.this._bufferSize);
294                 super.setMinCompressSize(GzipHandler.this._minGzipSize);
295             }
296             
297             @Override
298             protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException
299             {
300                 return new AbstractCompressedStream("gzip",request,response,contentLength,bufferSize,minCompressSize)
301                 {
302                     @Override
303                     protected DeflaterOutputStream createStream() throws IOException
304                     {
305                         return new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
306                     }
307                 };
308             }
309             
310             @Override
311             protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
312             {
313                 return GzipHandler.this.newWriter(out,encoding);
314             }
315         };
316     }
317     
318     /**
319      * Allows derived implementations to replace PrintWriter implementation.
320      *
321      * @param out the out
322      * @param encoding the encoding
323      * @return the prints the writer
324      * @throws UnsupportedEncodingException
325      */
326     protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
327     {
328         return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
329     }
330 }