1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server.handler.gzip;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.Set;
24 import java.util.zip.Deflater;
25
26 import javax.servlet.ServletContext;
27 import javax.servlet.ServletException;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
30
31 import org.eclipse.jetty.http.GzipHttpContent;
32 import org.eclipse.jetty.http.HttpField;
33 import org.eclipse.jetty.http.HttpHeader;
34 import org.eclipse.jetty.http.HttpMethod;
35 import org.eclipse.jetty.http.HttpVersion;
36 import org.eclipse.jetty.http.MimeTypes;
37 import org.eclipse.jetty.http.pathmap.PathSpecSet;
38 import org.eclipse.jetty.server.HttpOutput;
39 import org.eclipse.jetty.server.Request;
40 import org.eclipse.jetty.server.handler.HandlerWrapper;
41 import org.eclipse.jetty.util.IncludeExclude;
42 import org.eclipse.jetty.util.RegexSet;
43 import org.eclipse.jetty.util.StringUtil;
44 import org.eclipse.jetty.util.URIUtil;
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 public class GzipHandler extends HandlerWrapper implements GzipFactory
59 {
60 private static final Logger LOG = Log.getLogger(GzipHandler.class);
61
62 public final static String GZIP = "gzip";
63 public final static String DEFLATE = "deflate";
64 public final static int DEFAULT_MIN_GZIP_SIZE=16;
65 private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
66 private int _compressionLevel=Deflater.DEFAULT_COMPRESSION;
67 private boolean _checkGzExists = true;
68 private boolean _syncFlush = false;
69
70
71 private final ThreadLocal<Deflater> _deflater = new ThreadLocal<>();
72
73 private final IncludeExclude<String> _agentPatterns=new IncludeExclude<>(RegexSet.class);
74 private final IncludeExclude<String> _methods = new IncludeExclude<>();
75 private final IncludeExclude<String> _paths = new IncludeExclude<>(PathSpecSet.class);
76 private final IncludeExclude<String> _mimeTypes = new IncludeExclude<>();
77
78 private HttpField _vary;
79
80
81
82
83
84
85
86
87
88
89 public GzipHandler()
90 {
91 _methods.include(HttpMethod.GET.asString());
92 for (String type:MimeTypes.getKnownMimeTypes())
93 {
94 if ("image/svg+xml".equals(type))
95 _paths.exclude("*.svgz");
96 else if (type.startsWith("image/")||
97 type.startsWith("audio/")||
98 type.startsWith("video/"))
99 _mimeTypes.exclude(type);
100 }
101 _mimeTypes.exclude("application/compress");
102 _mimeTypes.exclude("application/zip");
103 _mimeTypes.exclude("application/gzip");
104 _mimeTypes.exclude("application/bzip2");
105 _mimeTypes.exclude("application/x-rar-compressed");
106 LOG.debug("{} mime types {}",this,_mimeTypes);
107
108 _agentPatterns.exclude(".*MSIE 6.0.*");
109 }
110
111
112
113
114
115 public void addExcludedAgentPatterns(String... patterns)
116 {
117 _agentPatterns.exclude(patterns);
118 }
119
120
121
122
123
124 public void addExcludedMethods(String... methods)
125 {
126 for (String m : methods)
127 _methods.exclude(m);
128 }
129
130
131
132
133
134
135
136
137 public void addExcludedMimeTypes(String... types)
138 {
139 for (String t : types)
140 _mimeTypes.exclude(StringUtil.csvSplit(t));
141 }
142
143
144
145
146
147
148
149
150
151 public void addExcludedPaths(String... pathspecs)
152 {
153 for (String p : pathspecs)
154 _paths.exclude(StringUtil.csvSplit(p));
155 }
156
157
158
159
160
161 public void addIncludedAgentPatterns(String... patterns)
162 {
163 _agentPatterns.include(patterns);
164 }
165
166
167
168
169
170 public void addIncludedMethods(String... methods)
171 {
172 for (String m : methods)
173 _methods.include(m);
174 }
175
176
177
178
179
180 public boolean isSyncFlush()
181 {
182 return _syncFlush;
183 }
184
185
186
187
188
189
190
191
192 public void setSyncFlush(boolean syncFlush)
193 {
194 _syncFlush = syncFlush;
195 }
196
197
198
199
200
201
202
203
204
205 public void addIncludedMimeTypes(String... types)
206 {
207 for (String t : types)
208 _mimeTypes.include(StringUtil.csvSplit(t));
209 }
210
211
212
213
214
215
216
217
218
219
220 public void addIncludedPaths(String... pathspecs)
221 {
222 for (String p : pathspecs)
223 _paths.include(StringUtil.csvSplit(p));
224 }
225
226
227 @Override
228 protected void doStart() throws Exception
229 {
230 _vary=(_agentPatterns.size()>0)?GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING_USER_AGENT:GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING;
231 super.doStart();
232 }
233
234
235 public boolean getCheckGzExists()
236 {
237 return _checkGzExists;
238 }
239
240
241 public int getCompressionLevel()
242 {
243 return _compressionLevel;
244 }
245
246
247 @Override
248 public Deflater getDeflater(Request request, long content_length)
249 {
250 String ua = request.getHttpFields().get(HttpHeader.USER_AGENT);
251 if (ua!=null && !isAgentGzipable(ua))
252 {
253 LOG.debug("{} excluded user agent {}",this,request);
254 return null;
255 }
256
257 if (content_length>=0 && content_length<_minGzipSize)
258 {
259 LOG.debug("{} excluded minGzipSize {}",this,request);
260 return null;
261 }
262
263
264 if (request.getHttpVersion()!=HttpVersion.HTTP_2)
265 {
266 HttpField accept = request.getHttpFields().getField(HttpHeader.ACCEPT_ENCODING);
267
268 if (accept==null)
269 {
270 LOG.debug("{} excluded !accept {}",this,request);
271 return null;
272 }
273 boolean gzip = accept.contains("gzip");
274
275 if (!gzip)
276 {
277 LOG.debug("{} excluded not gzip accept {}",this,request);
278 return null;
279 }
280 }
281
282 Deflater df = _deflater.get();
283 if (df==null)
284 df=new Deflater(_compressionLevel,true);
285 else
286 _deflater.set(null);
287
288 return df;
289 }
290
291
292 public String[] getExcludedAgentPatterns()
293 {
294 Set<String> excluded=_agentPatterns.getExcluded();
295 return excluded.toArray(new String[excluded.size()]);
296 }
297
298
299 public String[] getExcludedMethods()
300 {
301 Set<String> excluded=_methods.getExcluded();
302 return excluded.toArray(new String[excluded.size()]);
303 }
304
305
306 public String[] getExcludedMimeTypes()
307 {
308 Set<String> excluded=_mimeTypes.getExcluded();
309 return excluded.toArray(new String[excluded.size()]);
310 }
311
312
313 public String[] getExcludedPaths()
314 {
315 Set<String> excluded=_paths.getExcluded();
316 return excluded.toArray(new String[excluded.size()]);
317 }
318
319
320 public String[] getIncludedAgentPatterns()
321 {
322 Set<String> includes=_agentPatterns.getIncluded();
323 return includes.toArray(new String[includes.size()]);
324 }
325
326
327 public String[] getIncludedMethods()
328 {
329 Set<String> includes=_methods.getIncluded();
330 return includes.toArray(new String[includes.size()]);
331 }
332
333
334 public String[] getIncludedMimeTypes()
335 {
336 Set<String> includes=_mimeTypes.getIncluded();
337 return includes.toArray(new String[includes.size()]);
338 }
339
340
341 public String[] getIncludedPaths()
342 {
343 Set<String> includes=_paths.getIncluded();
344 return includes.toArray(new String[includes.size()]);
345 }
346
347
348 @Deprecated
349 public String[] getMethods()
350 {
351 return getIncludedMethods();
352 }
353
354
355
356
357
358
359
360 public int getMinGzipSize()
361 {
362 return _minGzipSize;
363 }
364
365 protected HttpField getVaryField()
366 {
367 return _vary;
368 }
369
370
371
372
373
374 @Override
375 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
376 {
377 ServletContext context = baseRequest.getServletContext();
378 String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo());
379 LOG.debug("{} handle {} in {}",this,baseRequest,context);
380
381 HttpOutput out = baseRequest.getResponse().getHttpOutput();
382
383 HttpOutput.Interceptor interceptor = out.getInterceptor();
384 while (interceptor!=null)
385 {
386 if (interceptor instanceof GzipHttpOutputInterceptor)
387 {
388 LOG.debug("{} already intercepting {}",this,request);
389 _handler.handle(target,baseRequest, request, response);
390 return;
391 }
392 interceptor=interceptor.getNextInterceptor();
393 }
394
395
396 if (!_methods.matches(baseRequest.getMethod()))
397 {
398 LOG.debug("{} excluded by method {}",this,request);
399 _handler.handle(target,baseRequest, request, response);
400 return;
401 }
402
403
404
405 if (!isPathGzipable(path))
406 {
407 LOG.debug("{} excluded by path {}",this,request);
408 _handler.handle(target,baseRequest, request, response);
409 return;
410 }
411
412
413 String mimeType = context==null?null:context.getMimeType(path);
414 if (mimeType!=null)
415 {
416 mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
417 if (!isMimeTypeGzipable(mimeType))
418 {
419 LOG.debug("{} excluded by path suffix mime type {}",this,request);
420
421 _handler.handle(target,baseRequest, request, response);
422 return;
423 }
424 }
425
426 if (_checkGzExists && context!=null)
427 {
428 String realpath=request.getServletContext().getRealPath(path);
429 if (realpath!=null)
430 {
431 File gz=new File(realpath+".gz");
432 if (gz.exists())
433 {
434 LOG.debug("{} gzip exists {}",this,request);
435
436 _handler.handle(target,baseRequest, request, response);
437 return;
438 }
439 }
440 }
441
442
443 String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
444 if (etag!=null)
445 {
446 int i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE);
447 if (i>0)
448 {
449 while (i>=0)
450 {
451 etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length());
452 i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE,i);
453 }
454 baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag));
455 }
456 }
457
458
459 out.setInterceptor(new GzipHttpOutputInterceptor(this,getVaryField(),baseRequest.getHttpChannel(),out.getInterceptor(),isSyncFlush()));
460
461 if (_handler!=null)
462 _handler.handle(target,baseRequest, request, response);
463 }
464
465
466
467
468
469
470
471
472 protected boolean isAgentGzipable(String ua)
473 {
474 if (ua == null)
475 return false;
476
477 return _agentPatterns.matches(ua);
478 }
479
480
481 @Override
482 public boolean isMimeTypeGzipable(String mimetype)
483 {
484 return _mimeTypes.matches(mimetype);
485 }
486
487
488
489
490
491
492
493
494
495 protected boolean isPathGzipable(String requestURI)
496 {
497 if (requestURI == null)
498 return true;
499
500 return _paths.matches(requestURI);
501 }
502
503
504 @Override
505 public void recycle(Deflater deflater)
506 {
507 deflater.reset();
508 if (_deflater.get()==null)
509 _deflater.set(deflater);
510 }
511
512
513
514
515
516
517 public void setCheckGzExists(boolean checkGzExists)
518 {
519 _checkGzExists = checkGzExists;
520 }
521
522
523
524
525
526 public void setCompressionLevel(int compressionLevel)
527 {
528 _compressionLevel = compressionLevel;
529 }
530
531
532
533
534
535 public void setExcludedAgentPatterns(String... patterns)
536 {
537 _agentPatterns.getExcluded().clear();
538 addExcludedAgentPatterns(patterns);
539 }
540
541
542
543
544
545 public void setExcludedMethods(String... method)
546 {
547 _methods.getExcluded().clear();
548 _methods.exclude(method);
549 }
550
551
552
553
554
555
556 public void setExcludedMimeTypes(String... types)
557 {
558 _mimeTypes.getExcluded().clear();
559 _mimeTypes.exclude(types);
560 }
561
562
563
564
565
566
567
568 public void setExcludedPaths(String... pathspecs)
569 {
570 _paths.getExcluded().clear();
571 _paths.exclude(pathspecs);
572 }
573
574
575
576
577
578 public void setIncludedAgentPatterns(String... patterns)
579 {
580 _agentPatterns.getIncluded().clear();
581 addIncludedAgentPatterns(patterns);
582 }
583
584
585
586
587
588 public void setIncludedMethods(String... methods)
589 {
590 _methods.getIncluded().clear();
591 _methods.include(methods);
592 }
593
594
595
596
597
598
599
600 public void setIncludedMimeTypes(String... types)
601 {
602 _mimeTypes.getIncluded().clear();
603 _mimeTypes.include(types);
604 }
605
606
607
608
609
610
611
612
613 public void setIncludedPaths(String... pathspecs)
614 {
615 _paths.getIncluded().clear();
616 _paths.include(pathspecs);
617 }
618
619
620
621
622
623
624
625 public void setMinGzipSize(int minGzipSize)
626 {
627 _minGzipSize = minGzipSize;
628 }
629 }