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.File;
22 import java.io.IOException;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.Set;
27 import java.util.StringTokenizer;
28 import java.util.regex.Pattern;
29 import java.util.zip.Deflater;
30
31 import javax.servlet.FilterChain;
32 import javax.servlet.FilterConfig;
33 import javax.servlet.ServletContext;
34 import javax.servlet.ServletException;
35 import javax.servlet.ServletRequest;
36 import javax.servlet.ServletResponse;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
39
40 import org.eclipse.jetty.http.HttpField;
41 import org.eclipse.jetty.http.HttpFields;
42 import org.eclipse.jetty.http.HttpGenerator;
43 import org.eclipse.jetty.http.HttpHeader;
44 import org.eclipse.jetty.http.HttpMethod;
45 import org.eclipse.jetty.http.MimeTypes;
46 import org.eclipse.jetty.server.HttpChannel;
47 import org.eclipse.jetty.server.HttpOutput;
48 import org.eclipse.jetty.server.Request;
49 import org.eclipse.jetty.servlets.gzip.GzipFactory;
50 import org.eclipse.jetty.servlets.gzip.GzipHttpOutput;
51 import org.eclipse.jetty.util.URIUtil;
52 import org.eclipse.jetty.util.log.Log;
53 import org.eclipse.jetty.util.log.Logger;
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 public class AsyncGzipFilter extends UserAgentFilter implements GzipFactory
128 {
129 private static final Logger LOG = Log.getLogger(GzipFilter.class);
130 public final static String GZIP = "gzip";
131 public static final String DEFLATE = "deflate";
132 public final static String ETAG_GZIP="--gzip";
133 public final static String ETAG = "o.e.j.s.GzipFilter.ETag";
134 public final static int DEFAULT_MIN_GZIP_SIZE=256;
135
136 protected ServletContext _context;
137 protected final Set<String> _mimeTypes=new HashSet<>();
138 protected boolean _excludeMimeTypes;
139 protected int _bufferSize=32*1024;
140 protected int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
141 protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
142 protected boolean _deflateNoWrap = true;
143 protected boolean _checkGzExists = true;
144
145
146 protected final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
147
148 protected final static ThreadLocal<byte[]> _buffer= new ThreadLocal<byte[]>();
149
150 protected final Set<String> _methods=new HashSet<String>();
151 protected Set<String> _excludedAgents;
152 protected Set<Pattern> _excludedAgentPatterns;
153 protected Set<String> _excludedPaths;
154 protected Set<Pattern> _excludedPathPatterns;
155 protected HttpField _vary=new HttpGenerator.CachedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING+", "+HttpHeader.USER_AGENT);
156
157
158
159
160
161 @Override
162 public void init(FilterConfig filterConfig) throws ServletException
163 {
164 super.init(filterConfig);
165
166 _context=filterConfig.getServletContext();
167
168 String tmp=filterConfig.getInitParameter("bufferSize");
169 if (tmp!=null)
170 _bufferSize=Integer.parseInt(tmp);
171 LOG.debug("{} bufferSize={}",this,_bufferSize);
172
173 tmp=filterConfig.getInitParameter("minGzipSize");
174 if (tmp!=null)
175 _minGzipSize=Integer.parseInt(tmp);
176 LOG.debug("{} minGzipSize={}",this,_minGzipSize);
177
178 tmp=filterConfig.getInitParameter("deflateCompressionLevel");
179 if (tmp!=null)
180 _deflateCompressionLevel=Integer.parseInt(tmp);
181 LOG.debug("{} deflateCompressionLevel={}",this,_deflateCompressionLevel);
182
183 tmp=filterConfig.getInitParameter("deflateNoWrap");
184 if (tmp!=null)
185 _deflateNoWrap=Boolean.parseBoolean(tmp);
186 LOG.debug("{} deflateNoWrap={}",this,_deflateNoWrap);
187
188 tmp=filterConfig.getInitParameter("checkGzExists");
189 if (tmp!=null)
190 _checkGzExists=Boolean.parseBoolean(tmp);
191 LOG.debug("{} checkGzExists={}",this,_checkGzExists);
192
193 tmp=filterConfig.getInitParameter("methods");
194 if (tmp!=null)
195 {
196 StringTokenizer tok = new StringTokenizer(tmp,",",false);
197 while (tok.hasMoreTokens())
198 _methods.add(tok.nextToken().trim().toUpperCase(Locale.ENGLISH));
199 }
200 else
201 _methods.add(HttpMethod.GET.asString());
202 LOG.debug("{} methods={}",this,_methods);
203
204 tmp=filterConfig.getInitParameter("mimeTypes");
205 if (tmp==null)
206 {
207 _excludeMimeTypes=true;
208 tmp=filterConfig.getInitParameter("excludedMimeTypes");
209 if (tmp==null)
210 {
211 for (String type:MimeTypes.getKnownMimeTypes())
212 {
213 if (type.equals("image/svg+xml"))
214 continue;
215 if (type.startsWith("image/")||
216 type.startsWith("audio/")||
217 type.startsWith("video/"))
218 _mimeTypes.add(type);
219 }
220 _mimeTypes.add("application/compress");
221 _mimeTypes.add("application/zip");
222 _mimeTypes.add("application/gzip");
223 }
224 else
225 {
226 StringTokenizer tok = new StringTokenizer(tmp,",",false);
227 while (tok.hasMoreTokens())
228 _mimeTypes.add(tok.nextToken().trim());
229 }
230 }
231 else
232 {
233 StringTokenizer tok = new StringTokenizer(tmp,",",false);
234 while (tok.hasMoreTokens())
235 _mimeTypes.add(tok.nextToken().trim());
236 }
237 LOG.debug("{} mimeTypes={}",this,_mimeTypes);
238 LOG.debug("{} excludeMimeTypes={}",this,_excludeMimeTypes);
239 tmp=filterConfig.getInitParameter("excludedAgents");
240 if (tmp!=null)
241 {
242 _excludedAgents=new HashSet<String>();
243 StringTokenizer tok = new StringTokenizer(tmp,",",false);
244 while (tok.hasMoreTokens())
245 _excludedAgents.add(tok.nextToken().trim());
246 }
247 LOG.debug("{} excludedAgents={}",this,_excludedAgents);
248
249 tmp=filterConfig.getInitParameter("excludeAgentPatterns");
250 if (tmp!=null)
251 {
252 _excludedAgentPatterns=new HashSet<Pattern>();
253 StringTokenizer tok = new StringTokenizer(tmp,",",false);
254 while (tok.hasMoreTokens())
255 _excludedAgentPatterns.add(Pattern.compile(tok.nextToken().trim()));
256 }
257 LOG.debug("{} excludedAgentPatterns={}",this,_excludedAgentPatterns);
258
259 tmp=filterConfig.getInitParameter("excludePaths");
260 if (tmp!=null)
261 {
262 _excludedPaths=new HashSet<String>();
263 StringTokenizer tok = new StringTokenizer(tmp,",",false);
264 while (tok.hasMoreTokens())
265 _excludedPaths.add(tok.nextToken().trim());
266 }
267 LOG.debug("{} excludedPaths={}",this,_excludedPaths);
268
269 tmp=filterConfig.getInitParameter("excludePathPatterns");
270 if (tmp!=null)
271 {
272 _excludedPathPatterns=new HashSet<Pattern>();
273 StringTokenizer tok = new StringTokenizer(tmp,",",false);
274 while (tok.hasMoreTokens())
275 _excludedPathPatterns.add(Pattern.compile(tok.nextToken().trim()));
276 }
277 LOG.debug("{} excludedPathPatterns={}",this,_excludedPathPatterns);
278
279 tmp=filterConfig.getInitParameter("vary");
280 if (tmp!=null)
281 _vary=new HttpGenerator.CachedHttpField(HttpHeader.VARY,tmp);
282 LOG.debug("{} vary={}",this,_vary);
283 }
284
285
286
287
288
289 @Override
290 public void destroy()
291 {
292 }
293
294
295
296
297
298 @Override
299 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
300 throws IOException, ServletException
301 {
302 LOG.debug("{} doFilter {}",this,req);
303 HttpServletRequest request=(HttpServletRequest)req;
304 HttpServletResponse response=(HttpServletResponse)res;
305
306
307 String requestURI = request.getRequestURI();
308 if (!_methods.contains(request.getMethod()))
309 {
310 LOG.debug("{} excluded by method {}",this,request);
311 super.doFilter(request,response,chain);
312 return;
313 }
314
315 if (isExcludedPath(requestURI))
316 {
317 LOG.debug("{} excluded by path {}",this,request);
318 super.doFilter(request,response,chain);
319 return;
320 }
321
322
323 if (_mimeTypes.size()>0 && _excludeMimeTypes)
324 {
325 String mimeType = _context.getMimeType(request.getRequestURI());
326
327 if (mimeType!=null)
328 {
329 mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
330 if (_mimeTypes.contains(mimeType))
331 {
332 LOG.debug("{} excluded by path suffix {}",this,request);
333
334 super.doFilter(request,response,chain);
335 return;
336 }
337 }
338 }
339
340
341 if (response.getHeader("Content-Encoding") != null)
342 {
343 super.doFilter(request,response,chain);
344 return;
345 }
346
347 if (_checkGzExists && request.getServletContext()!=null)
348 {
349 String path=request.getServletContext().getRealPath(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()));
350 if (path!=null)
351 {
352 File gz=new File(path+".gz");
353 if (gz.exists())
354 {
355 LOG.debug("{} gzip exists {}",this,request);
356
357 super.doFilter(request,response,chain);
358 return;
359 }
360 }
361 }
362
363
364 String etag = request.getHeader("If-None-Match");
365 if (etag!=null)
366 {
367 if (etag.contains(ETAG_GZIP))
368 request.setAttribute(ETAG,etag.replace(ETAG_GZIP,""));
369 }
370
371 HttpChannel<?> channel = HttpChannel.getCurrentHttpChannel();
372 HttpOutput out = channel.getResponse().getHttpOutput();
373 if (!(out instanceof GzipHttpOutput))
374 {
375 if (out.getClass()!=HttpOutput.class)
376 throw new IllegalStateException();
377 channel.getResponse().setHttpOutput(out = new GzipHttpOutput(channel));
378 }
379
380 GzipHttpOutput cout=(GzipHttpOutput)out;
381
382 try
383 {
384 cout.mightCompress(this);
385 super.doFilter(request,response,chain);
386 }
387 catch(Throwable e)
388 {
389 LOG.debug("{} excepted {}",this,request,e);
390 if (!response.isCommitted())
391 {
392 cout.resetBuffer();
393 cout.noCompressionIfPossible();
394 }
395 throw e;
396 }
397 }
398
399
400
401
402
403
404
405
406
407 private boolean isExcludedAgent(String ua)
408 {
409 if (ua == null)
410 return false;
411
412 if (_excludedAgents != null)
413 {
414 if (_excludedAgents.contains(ua))
415 {
416 return true;
417 }
418 }
419 if (_excludedAgentPatterns != null)
420 {
421 for (Pattern pattern : _excludedAgentPatterns)
422 {
423 if (pattern.matcher(ua).matches())
424 {
425 return true;
426 }
427 }
428 }
429
430 return false;
431 }
432
433
434
435
436
437
438
439
440 private boolean isExcludedPath(String requestURI)
441 {
442 if (requestURI == null)
443 return false;
444 if (_excludedPaths != null)
445 {
446 for (String excludedPath : _excludedPaths)
447 {
448 if (requestURI.startsWith(excludedPath))
449 {
450 return true;
451 }
452 }
453 }
454 if (_excludedPathPatterns != null)
455 {
456 for (Pattern pattern : _excludedPathPatterns)
457 {
458 if (pattern.matcher(requestURI).matches())
459 {
460 return true;
461 }
462 }
463 }
464 return false;
465 }
466
467 @Override
468 public HttpField getVaryField()
469 {
470 return _vary;
471 }
472
473 @Override
474 public Deflater getDeflater(Request request, long content_length)
475 {
476 String ua = getUserAgent(request);
477 if (ua!=null && isExcludedAgent(ua))
478 {
479 LOG.debug("{} excluded user agent {}",this,request);
480 return null;
481 }
482
483 if (content_length>=0 && content_length<_minGzipSize)
484 {
485 LOG.debug("{} excluded minGzipSize {}",this,request);
486 return null;
487 }
488
489 String accept = request.getHttpFields().get(HttpHeader.ACCEPT_ENCODING);
490 if (accept==null)
491 {
492 LOG.debug("{} excluded !accept {}",this,request);
493 return null;
494 }
495
496 boolean gzip=false;
497 if (GZIP.equals(accept) || accept.startsWith("gzip,"))
498 gzip=true;
499 else
500 {
501 List<String> list=HttpFields.qualityList(request.getHttpFields().getValues(HttpHeader.ACCEPT_ENCODING.asString(),","));
502 for (String a:list)
503 {
504 if (GZIP.equalsIgnoreCase(HttpFields.valueParameters(a,null)))
505 {
506 gzip=true;
507 break;
508 }
509 }
510 }
511
512 if (!gzip)
513 {
514 LOG.debug("{} excluded not gzip accept {}",this,request);
515 return null;
516 }
517
518 Deflater df = _deflater.get();
519 if (df==null)
520 df=new Deflater(_deflateCompressionLevel,_deflateNoWrap);
521 else
522 _deflater.set(null);
523
524 return df;
525 }
526
527 @Override
528 public void recycle(Deflater deflater)
529 {
530 deflater.reset();
531 if (_deflater.get()==null)
532 _deflater.set(deflater);
533
534 }
535
536 @Override
537 public boolean isExcludedMimeType(String mimetype)
538 {
539 return _mimeTypes.contains(mimetype) == _excludeMimeTypes;
540 }
541
542 @Override
543 public int getBufferSize()
544 {
545 return _bufferSize;
546 }
547
548
549 }