1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.servlets;
20
21 import java.io.IOException;
22 import java.util.HashSet;
23 import java.util.Locale;
24 import java.util.Set;
25 import java.util.StringTokenizer;
26 import java.util.regex.Pattern;
27 import java.util.zip.Deflater;
28 import java.util.zip.DeflaterOutputStream;
29 import java.util.zip.GZIPOutputStream;
30
31 import javax.servlet.FilterChain;
32 import javax.servlet.FilterConfig;
33 import javax.servlet.ServletException;
34 import javax.servlet.ServletRequest;
35 import javax.servlet.ServletResponse;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38
39 import org.eclipse.jetty.continuation.Continuation;
40 import org.eclipse.jetty.continuation.ContinuationListener;
41 import org.eclipse.jetty.continuation.ContinuationSupport;
42 import org.eclipse.jetty.http.HttpMethods;
43 import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
44 import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
45 import org.eclipse.jetty.util.log.Log;
46 import org.eclipse.jetty.util.log.Logger;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 public class GzipFilter extends UserAgentFilter
106 {
107 private static final Logger LOG = Log.getLogger(GzipFilter.class);
108 public final static String GZIP="gzip";
109 public final static String DEFLATE="deflate";
110
111
112 protected Set<String> _mimeTypes;
113 protected int _bufferSize=8192;
114 protected int _minGzipSize=256;
115 protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
116 protected boolean _deflateNoWrap = true;
117 protected Set<String> _excludedAgents;
118 protected Set<Pattern> _excludedAgentPatterns;
119 protected Set<String> _excludedPaths;
120 protected Set<Pattern> _excludedPathPatterns;
121
122 private static final int STATE_SEPARATOR = 0;
123 private static final int STATE_Q = 1;
124 private static final int STATE_QVALUE = 2;
125 private static final int STATE_DEFAULT = 3;
126
127
128
129
130
131
132 @Override
133 public void init(FilterConfig filterConfig) throws ServletException
134 {
135 super.init(filterConfig);
136
137 String tmp=filterConfig.getInitParameter("bufferSize");
138 if (tmp!=null)
139 _bufferSize=Integer.parseInt(tmp);
140
141 tmp=filterConfig.getInitParameter("minGzipSize");
142 if (tmp!=null)
143 _minGzipSize=Integer.parseInt(tmp);
144
145 tmp=filterConfig.getInitParameter("deflateCompressionLevel");
146 if (tmp!=null)
147 _deflateCompressionLevel=Integer.parseInt(tmp);
148
149 tmp=filterConfig.getInitParameter("deflateNoWrap");
150 if (tmp!=null)
151 _deflateNoWrap=Boolean.parseBoolean(tmp);
152
153 tmp=filterConfig.getInitParameter("mimeTypes");
154 if (tmp!=null)
155 {
156 _mimeTypes=new HashSet<String>();
157 StringTokenizer tok = new StringTokenizer(tmp,",",false);
158 while (tok.hasMoreTokens())
159 _mimeTypes.add(tok.nextToken());
160 }
161 tmp=filterConfig.getInitParameter("excludedAgents");
162 if (tmp!=null)
163 {
164 _excludedAgents=new HashSet<String>();
165 StringTokenizer tok = new StringTokenizer(tmp,",",false);
166 while (tok.hasMoreTokens())
167 _excludedAgents.add(tok.nextToken());
168 }
169
170 tmp=filterConfig.getInitParameter("excludeAgentPatterns");
171 if (tmp!=null)
172 {
173 _excludedAgentPatterns=new HashSet<Pattern>();
174 StringTokenizer tok = new StringTokenizer(tmp,",",false);
175 while (tok.hasMoreTokens())
176 _excludedAgentPatterns.add(Pattern.compile(tok.nextToken()));
177 }
178
179 tmp=filterConfig.getInitParameter("excludePaths");
180 if (tmp!=null)
181 {
182 _excludedPaths=new HashSet<String>();
183 StringTokenizer tok = new StringTokenizer(tmp,",",false);
184 while (tok.hasMoreTokens())
185 _excludedPaths.add(tok.nextToken());
186 }
187
188 tmp=filterConfig.getInitParameter("excludePathPatterns");
189 if (tmp!=null)
190 {
191 _excludedPathPatterns=new HashSet<Pattern>();
192 StringTokenizer tok = new StringTokenizer(tmp,",",false);
193 while (tok.hasMoreTokens())
194 _excludedPathPatterns.add(Pattern.compile(tok.nextToken()));
195 }
196 }
197
198
199
200
201
202 @Override
203 public void destroy()
204 {
205 }
206
207
208
209
210
211 @Override
212 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
213 throws IOException, ServletException
214 {
215 HttpServletRequest request=(HttpServletRequest)req;
216 HttpServletResponse response=(HttpServletResponse)res;
217
218
219 response.setHeader("Vary","Accept-Encoding");
220
221
222 String compressionType = selectCompression(request.getHeader("accept-encoding"));
223 if (compressionType!=null && !response.containsHeader("Content-Encoding") && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod()))
224 {
225 String ua = getUserAgent(request);
226 if (isExcludedAgent(ua))
227 {
228 super.doFilter(request,response,chain);
229 return;
230 }
231 String requestURI = request.getRequestURI();
232 if (isExcludedPath(requestURI))
233 {
234 super.doFilter(request,response,chain);
235 return;
236 }
237
238 CompressedResponseWrapper wrappedResponse = createWrappedResponse(request,response,compressionType);
239
240 boolean exceptional=true;
241 try
242 {
243 super.doFilter(request,wrappedResponse,chain);
244 exceptional=false;
245 }
246 finally
247 {
248 Continuation continuation = ContinuationSupport.getContinuation(request);
249 if (continuation.isSuspended() && continuation.isResponseWrapped())
250 {
251 continuation.addContinuationListener(new ContinuationListenerWaitingForWrappedResponseToFinish(wrappedResponse));
252 }
253 else if (exceptional && !response.isCommitted())
254 {
255 wrappedResponse.resetBuffer();
256 wrappedResponse.noCompression();
257 }
258 else
259 wrappedResponse.finish();
260 }
261 }
262 else
263 {
264 super.doFilter(request,response,chain);
265 }
266 }
267
268
269 private String selectCompression(String encodingHeader)
270 {
271
272
273 String compression = null;
274 if (encodingHeader!=null)
275 {
276
277 String[] encodings = getEncodings(encodingHeader);
278 if (encodings != null)
279 {
280 for (int i=0; i< encodings.length; i++)
281 {
282 if (encodings[i].toLowerCase(Locale.ENGLISH).contains(GZIP))
283 {
284 if (isEncodingAcceptable(encodings[i]))
285 {
286 compression = GZIP;
287 break;
288 }
289 }
290
291 if (encodings[i].toLowerCase(Locale.ENGLISH).contains(DEFLATE))
292 {
293 if (isEncodingAcceptable(encodings[i]))
294 {
295 compression = DEFLATE;
296 }
297 }
298 }
299 }
300 }
301 return compression;
302 }
303
304
305 private String[] getEncodings (String encodingHeader)
306 {
307 if (encodingHeader == null)
308 return null;
309 return encodingHeader.split(",");
310 }
311
312 private boolean isEncodingAcceptable(String encoding)
313 {
314 int state = STATE_DEFAULT;
315 int qvalueIdx = -1;
316 for (int i=0;i<encoding.length();i++)
317 {
318 char c = encoding.charAt(i);
319 switch (state)
320 {
321 case STATE_DEFAULT:
322 {
323 if (';' == c)
324 state = STATE_SEPARATOR;
325 break;
326 }
327 case STATE_SEPARATOR:
328 {
329 if ('q' == c || 'Q' == c)
330 state = STATE_Q;
331 break;
332 }
333 case STATE_Q:
334 {
335 if ('=' == c)
336 state = STATE_QVALUE;
337 break;
338 }
339 case STATE_QVALUE:
340 {
341 if (qvalueIdx < 0 && '0' == c || '1' == c)
342 qvalueIdx = i;
343 break;
344 }
345 }
346 }
347
348 if (qvalueIdx < 0)
349 return true;
350
351 if ("0".equals(encoding.substring(qvalueIdx).trim()))
352 return false;
353 return true;
354 }
355
356
357 protected CompressedResponseWrapper createWrappedResponse(HttpServletRequest request, HttpServletResponse response, final String compressionType)
358 {
359 CompressedResponseWrapper wrappedResponse = null;
360 if (compressionType.equals(GZIP))
361 {
362 wrappedResponse = new CompressedResponseWrapper(request,response)
363 {
364 @Override
365 protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException
366 {
367 return new AbstractCompressedStream(compressionType,request,response,contentLength,bufferSize,minCompressSize)
368 {
369 @Override
370 protected DeflaterOutputStream createStream() throws IOException
371 {
372 return new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
373 }
374 };
375 }
376 };
377 }
378 else if (compressionType.equals(DEFLATE))
379 {
380 wrappedResponse = new CompressedResponseWrapper(request,response)
381 {
382 @Override
383 protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minCompressSize) throws IOException
384 {
385 return new AbstractCompressedStream(compressionType,request,response,contentLength,bufferSize,minCompressSize)
386 {
387 @Override
388 protected DeflaterOutputStream createStream() throws IOException
389 {
390 return new DeflaterOutputStream(_response.getOutputStream(),new Deflater(_deflateCompressionLevel,_deflateNoWrap));
391 }
392 };
393 }
394 };
395 }
396 else
397 {
398 throw new IllegalStateException(compressionType + " not supported");
399 }
400 configureWrappedResponse(wrappedResponse);
401 return wrappedResponse;
402 }
403
404 protected void configureWrappedResponse(CompressedResponseWrapper wrappedResponse)
405 {
406 wrappedResponse.setMimeTypes(_mimeTypes);
407 wrappedResponse.setBufferSize(_bufferSize);
408 wrappedResponse.setMinCompressSize(_minGzipSize);
409 }
410
411 private class ContinuationListenerWaitingForWrappedResponseToFinish implements ContinuationListener{
412
413 private CompressedResponseWrapper wrappedResponse;
414
415 public ContinuationListenerWaitingForWrappedResponseToFinish(CompressedResponseWrapper wrappedResponse)
416 {
417 this.wrappedResponse = wrappedResponse;
418 }
419
420 public void onComplete(Continuation continuation)
421 {
422 try
423 {
424 wrappedResponse.finish();
425 }
426 catch (IOException e)
427 {
428 LOG.warn(e);
429 }
430 }
431
432 public void onTimeout(Continuation continuation)
433 {
434 }
435 }
436
437
438
439
440
441
442
443
444 private boolean isExcludedAgent(String ua)
445 {
446 if (ua == null)
447 return false;
448
449 if (_excludedAgents != null)
450 {
451 if (_excludedAgents.contains(ua))
452 {
453 return true;
454 }
455 }
456 if (_excludedAgentPatterns != null)
457 {
458 for (Pattern pattern : _excludedAgentPatterns)
459 {
460 if (pattern.matcher(ua).matches())
461 {
462 return true;
463 }
464 }
465 }
466
467 return false;
468 }
469
470
471
472
473
474
475
476
477 private boolean isExcludedPath(String requestURI)
478 {
479 if (requestURI == null)
480 return false;
481 if (_excludedPaths != null)
482 {
483 for (String excludedPath : _excludedPaths)
484 {
485 if (requestURI.startsWith(excludedPath))
486 {
487 return true;
488 }
489 }
490 }
491 if (_excludedPathPatterns != null)
492 {
493 for (Pattern pattern : _excludedPathPatterns)
494 {
495 if (pattern.matcher(requestURI).matches())
496 {
497 return true;
498 }
499 }
500 }
501 return false;
502 }
503 }