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 HttpField accept = request.getHttpFields().getField(HttpHeader.ACCEPT_ENCODING);
265
266 if (accept==null)
267 {
268 LOG.debug("{} excluded !accept {}",this,request);
269 return null;
270 }
271 boolean gzip = accept.contains("gzip");
272
273 if (!gzip)
274 {
275 LOG.debug("{} excluded not gzip accept {}",this,request);
276 return null;
277 }
278
279 Deflater df = _deflater.get();
280 if (df==null)
281 df=new Deflater(_compressionLevel,true);
282 else
283 _deflater.set(null);
284
285 return df;
286 }
287
288
289 public String[] getExcludedAgentPatterns()
290 {
291 Set<String> excluded=_agentPatterns.getExcluded();
292 return excluded.toArray(new String[excluded.size()]);
293 }
294
295
296 public String[] getExcludedMethods()
297 {
298 Set<String> excluded=_methods.getExcluded();
299 return excluded.toArray(new String[excluded.size()]);
300 }
301
302
303 public String[] getExcludedMimeTypes()
304 {
305 Set<String> excluded=_mimeTypes.getExcluded();
306 return excluded.toArray(new String[excluded.size()]);
307 }
308
309
310 public String[] getExcludedPaths()
311 {
312 Set<String> excluded=_paths.getExcluded();
313 return excluded.toArray(new String[excluded.size()]);
314 }
315
316
317 public String[] getIncludedAgentPatterns()
318 {
319 Set<String> includes=_agentPatterns.getIncluded();
320 return includes.toArray(new String[includes.size()]);
321 }
322
323
324 public String[] getIncludedMethods()
325 {
326 Set<String> includes=_methods.getIncluded();
327 return includes.toArray(new String[includes.size()]);
328 }
329
330
331 public String[] getIncludedMimeTypes()
332 {
333 Set<String> includes=_mimeTypes.getIncluded();
334 return includes.toArray(new String[includes.size()]);
335 }
336
337
338 public String[] getIncludedPaths()
339 {
340 Set<String> includes=_paths.getIncluded();
341 return includes.toArray(new String[includes.size()]);
342 }
343
344
345 @Deprecated
346 public String[] getMethods()
347 {
348 return getIncludedMethods();
349 }
350
351
352
353
354
355
356
357 public int getMinGzipSize()
358 {
359 return _minGzipSize;
360 }
361
362 protected HttpField getVaryField()
363 {
364 return _vary;
365 }
366
367
368
369
370
371 @Override
372 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
373 {
374 ServletContext context = baseRequest.getServletContext();
375 String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo());
376 LOG.debug("{} handle {} in {}",this,baseRequest,context);
377
378 HttpOutput out = baseRequest.getResponse().getHttpOutput();
379
380 HttpOutput.Interceptor interceptor = out.getInterceptor();
381 while (interceptor!=null)
382 {
383 if (interceptor instanceof GzipHttpOutputInterceptor)
384 {
385 LOG.debug("{} already intercepting {}",this,request);
386 _handler.handle(target,baseRequest, request, response);
387 return;
388 }
389 interceptor=interceptor.getNextInterceptor();
390 }
391
392
393 if (!_methods.matches(baseRequest.getMethod()))
394 {
395 LOG.debug("{} excluded by method {}",this,request);
396 _handler.handle(target,baseRequest, request, response);
397 return;
398 }
399
400
401
402 if (!isPathGzipable(path))
403 {
404 LOG.debug("{} excluded by path {}",this,request);
405 _handler.handle(target,baseRequest, request, response);
406 return;
407 }
408
409
410 String mimeType = context==null?null:context.getMimeType(path);
411 if (mimeType!=null)
412 {
413 mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
414 if (!isMimeTypeGzipable(mimeType))
415 {
416 LOG.debug("{} excluded by path suffix mime type {}",this,request);
417
418 _handler.handle(target,baseRequest, request, response);
419 return;
420 }
421 }
422
423 if (_checkGzExists && context!=null)
424 {
425 String realpath=request.getServletContext().getRealPath(path);
426 if (realpath!=null)
427 {
428 File gz=new File(realpath+".gz");
429 if (gz.exists())
430 {
431 LOG.debug("{} gzip exists {}",this,request);
432
433 _handler.handle(target,baseRequest, request, response);
434 return;
435 }
436 }
437 }
438
439
440 String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
441 if (etag!=null)
442 {
443 int i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE);
444 if (i>0)
445 {
446 while (i>=0)
447 {
448 etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length());
449 i=etag.indexOf(GzipHttpContent.ETAG_GZIP_QUOTE,i);
450 }
451 baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag));
452 }
453 }
454
455
456 out.setInterceptor(new GzipHttpOutputInterceptor(this,getVaryField(),baseRequest.getHttpChannel(),out.getInterceptor(),isSyncFlush()));
457
458 if (_handler!=null)
459 _handler.handle(target,baseRequest, request, response);
460 }
461
462
463
464
465
466
467
468
469 protected boolean isAgentGzipable(String ua)
470 {
471 if (ua == null)
472 return false;
473
474 return _agentPatterns.matches(ua);
475 }
476
477
478 @Override
479 public boolean isMimeTypeGzipable(String mimetype)
480 {
481 return _mimeTypes.matches(mimetype);
482 }
483
484
485
486
487
488
489
490
491
492 protected boolean isPathGzipable(String requestURI)
493 {
494 if (requestURI == null)
495 return true;
496
497 return _paths.matches(requestURI);
498 }
499
500
501 @Override
502 public void recycle(Deflater deflater)
503 {
504 deflater.reset();
505 if (_deflater.get()==null)
506 _deflater.set(deflater);
507 }
508
509
510
511
512
513
514 public void setCheckGzExists(boolean checkGzExists)
515 {
516 _checkGzExists = checkGzExists;
517 }
518
519
520
521
522
523 public void setCompressionLevel(int compressionLevel)
524 {
525 _compressionLevel = compressionLevel;
526 }
527
528
529
530
531
532 public void setExcludedAgentPatterns(String... patterns)
533 {
534 _agentPatterns.getExcluded().clear();
535 addExcludedAgentPatterns(patterns);
536 }
537
538
539
540
541
542 public void setExcludedMethods(String... method)
543 {
544 _methods.getExcluded().clear();
545 _methods.exclude(method);
546 }
547
548
549
550
551
552
553 public void setExcludedMimeTypes(String... types)
554 {
555 _mimeTypes.getExcluded().clear();
556 _mimeTypes.exclude(types);
557 }
558
559
560
561
562
563
564
565 public void setExcludedPaths(String... pathspecs)
566 {
567 _paths.getExcluded().clear();
568 _paths.exclude(pathspecs);
569 }
570
571
572
573
574
575 public void setIncludedAgentPatterns(String... patterns)
576 {
577 _agentPatterns.getIncluded().clear();
578 addIncludedAgentPatterns(patterns);
579 }
580
581
582
583
584
585 public void setIncludedMethods(String... methods)
586 {
587 _methods.getIncluded().clear();
588 _methods.include(methods);
589 }
590
591
592
593
594
595
596
597 public void setIncludedMimeTypes(String... types)
598 {
599 _mimeTypes.getIncluded().clear();
600 _mimeTypes.include(types);
601 }
602
603
604
605
606
607
608
609
610 public void setIncludedPaths(String... pathspecs)
611 {
612 _paths.getIncluded().clear();
613 _paths.include(pathspecs);
614 }
615
616
617
618
619
620
621
622 public void setMinGzipSize(int minGzipSize)
623 {
624 _minGzipSize = minGzipSize;
625 }
626 }