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 super.setMimeTypes(GzipHandler.this._mimeTypes);
285 super.setBufferSize(GzipHandler.this._bufferSize);
286 super.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 }