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