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