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