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 static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.Set;
26 import java.util.zip.Deflater;
27
28 import javax.servlet.ServletContext;
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.eclipse.jetty.http.GzipHttpContent;
34 import org.eclipse.jetty.http.HttpField;
35 import org.eclipse.jetty.http.HttpHeader;
36 import org.eclipse.jetty.http.HttpMethod;
37 import org.eclipse.jetty.http.HttpVersion;
38 import org.eclipse.jetty.http.MimeTypes;
39 import org.eclipse.jetty.http.pathmap.PathSpecSet;
40 import org.eclipse.jetty.server.HttpOutput;
41 import org.eclipse.jetty.server.Request;
42 import org.eclipse.jetty.server.handler.HandlerWrapper;
43 import org.eclipse.jetty.util.IncludeExclude;
44 import org.eclipse.jetty.util.RegexSet;
45 import org.eclipse.jetty.util.StringUtil;
46 import org.eclipse.jetty.util.URIUtil;
47 import org.eclipse.jetty.util.log.Log;
48 import org.eclipse.jetty.util.log.Logger;
49
50
51
52
53
54
55
56
57
58
59
60
61 public class GzipHandler extends HandlerWrapper implements GzipFactory
62 {
63 private static final Logger LOG = Log.getLogger(GzipHandler.class);
64
65 public final static String GZIP = "gzip";
66 public final static String DEFLATE = "deflate";
67 public final static int DEFAULT_MIN_GZIP_SIZE=16;
68 private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
69 private int _compressionLevel=Deflater.DEFAULT_COMPRESSION;
70 private boolean _checkGzExists = true;
71 private boolean _syncFlush = false;
72
73
74 private final ThreadLocal<Deflater> _deflater = new ThreadLocal<Deflater>();
75
76 private final IncludeExclude<String> _agentPatterns=new IncludeExclude<>(RegexSet.class);
77 private final IncludeExclude<String> _methods = new IncludeExclude<>();
78 private final IncludeExclude<String> _paths = new IncludeExclude<>(PathSpecSet.class);
79 private final IncludeExclude<String> _mimeTypes = new IncludeExclude<>();
80
81 private HttpField _vary;
82
83
84
85
86
87
88
89
90
91
92 public GzipHandler()
93 {
94 _methods.include(HttpMethod.GET.asString());
95 for (String type:MimeTypes.getKnownMimeTypes())
96 {
97 if ("image/svg+xml".equals(type))
98 _paths.exclude("*.svgz");
99 else if (type.startsWith("image/")||
100 type.startsWith("audio/")||
101 type.startsWith("video/"))
102 _mimeTypes.exclude(type);
103 }
104 _mimeTypes.exclude("application/compress");
105 _mimeTypes.exclude("application/zip");
106 _mimeTypes.exclude("application/gzip");
107 _mimeTypes.exclude("application/bzip2");
108 _mimeTypes.exclude("application/x-rar-compressed");
109 LOG.debug("{} mime types {}",this,_mimeTypes);
110
111 _agentPatterns.exclude(".*MSIE 6.0.*");
112 }
113
114
115
116
117
118 public void addExcludedAgentPatterns(String... patterns)
119 {
120 _agentPatterns.exclude(patterns);
121 }
122
123
124
125
126
127 public void addExcludedMethods(String... methods)
128 {
129 for (String m : methods)
130 _methods.exclude(m);
131 }
132
133
134
135
136
137
138
139
140 public void addExcludedMimeTypes(String... types)
141 {
142 for (String t : types)
143 _mimeTypes.exclude(StringUtil.csvSplit(t));
144 }
145
146
147
148
149
150
151
152
153
154 public void addExcludedPaths(String... pathspecs)
155 {
156 for (String p : pathspecs)
157 _paths.exclude(StringUtil.csvSplit(p));
158 }
159
160
161
162
163
164 public void addIncludedAgentPatterns(String... patterns)
165 {
166 _agentPatterns.include(patterns);
167 }
168
169
170
171
172
173 public void addIncludedMethods(String... methods)
174 {
175 for (String m : methods)
176 _methods.include(m);
177 }
178
179
180
181
182
183 public boolean isSyncFlush()
184 {
185 return _syncFlush;
186 }
187
188
189
190
191
192
193
194
195 public void setSyncFlush(boolean syncFlush)
196 {
197 _syncFlush = syncFlush;
198 }
199
200
201
202
203
204
205
206
207
208 public void addIncludedMimeTypes(String... types)
209 {
210 for (String t : types)
211 _mimeTypes.include(StringUtil.csvSplit(t));
212 }
213
214
215
216
217
218
219
220
221
222
223 public void addIncludedPaths(String... pathspecs)
224 {
225 for (String p : pathspecs)
226 _paths.include(StringUtil.csvSplit(p));
227 }
228
229
230 @Override
231 protected void doStart() throws Exception
232 {
233 _vary=(_agentPatterns.size()>0)?GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING_USER_AGENT:GzipHttpOutputInterceptor.VARY_ACCEPT_ENCODING;
234 super.doStart();
235 }
236
237
238 public boolean getCheckGzExists()
239 {
240 return _checkGzExists;
241 }
242
243
244 public int getCompressionLevel()
245 {
246 return _compressionLevel;
247 }
248
249
250 @Override
251 public Deflater getDeflater(Request request, long content_length)
252 {
253 String ua = request.getHttpFields().get(HttpHeader.USER_AGENT);
254 if (ua!=null && !isAgentGzipable(ua))
255 {
256 LOG.debug("{} excluded user agent {}",this,request);
257 return null;
258 }
259
260 if (content_length>=0 && content_length<_minGzipSize)
261 {
262 LOG.debug("{} excluded minGzipSize {}",this,request);
263 return null;
264 }
265
266
267 if (request.getHttpVersion()!=HttpVersion.HTTP_2)
268 {
269 HttpField accept = request.getHttpFields().getField(HttpHeader.ACCEPT_ENCODING);
270
271 if (accept==null)
272 {
273 LOG.debug("{} excluded !accept {}",this,request);
274 return null;
275 }
276 boolean gzip = accept.contains("gzip");
277
278 if (!gzip)
279 {
280 LOG.debug("{} excluded not gzip accept {}",this,request);
281 return null;
282 }
283 }
284
285 Deflater df = _deflater.get();
286 if (df==null)
287 df=new Deflater(_compressionLevel,true);
288 else
289 _deflater.set(null);
290
291 return df;
292 }
293
294
295 public String[] getExcludedAgentPatterns()
296 {
297 Set<String> excluded=_agentPatterns.getExcluded();
298 return excluded.toArray(new String[excluded.size()]);
299 }
300
301
302 public String[] getExcludedMethods()
303 {
304 Set<String> excluded=_methods.getExcluded();
305 return excluded.toArray(new String[excluded.size()]);
306 }
307
308
309 public String[] getExcludedMimeTypes()
310 {
311 Set<String> excluded=_mimeTypes.getExcluded();
312 return excluded.toArray(new String[excluded.size()]);
313 }
314
315
316 public String[] getExcludedPaths()
317 {
318 Set<String> excluded=_paths.getExcluded();
319 return excluded.toArray(new String[excluded.size()]);
320 }
321
322
323 public String[] getIncludedAgentPatterns()
324 {
325 Set<String> includes=_agentPatterns.getIncluded();
326 return includes.toArray(new String[includes.size()]);
327 }
328
329
330 public String[] getIncludedMethods()
331 {
332 Set<String> includes=_methods.getIncluded();
333 return includes.toArray(new String[includes.size()]);
334 }
335
336
337 public String[] getIncludedMimeTypes()
338 {
339 Set<String> includes=_mimeTypes.getIncluded();
340 return includes.toArray(new String[includes.size()]);
341 }
342
343
344 public String[] getIncludedPaths()
345 {
346 Set<String> includes=_paths.getIncluded();
347 return includes.toArray(new String[includes.size()]);
348 }
349
350
351 @Deprecated
352 public String[] getMethods()
353 {
354 return getIncludedMethods();
355 }
356
357
358
359
360
361
362
363 public int getMinGzipSize()
364 {
365 return _minGzipSize;
366 }
367
368
369
370
371
372 @Override
373 public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
374 {
375 ServletContext context = baseRequest.getServletContext();
376 String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo());
377 LOG.debug("{} handle {} in {}",this,baseRequest,context);
378
379 HttpOutput out = baseRequest.getResponse().getHttpOutput();
380
381 HttpOutput.Interceptor interceptor = out.getInterceptor();
382 while (interceptor!=null)
383 {
384 if (interceptor instanceof GzipHttpOutputInterceptor)
385 {
386 LOG.debug("{} already intercepting {}",this,request);
387 _handler.handle(target,baseRequest, request, response);
388 return;
389 }
390 interceptor=interceptor.getNextInterceptor();
391 }
392
393
394 if (!_methods.matches(baseRequest.getMethod()))
395 {
396 LOG.debug("{} excluded by method {}",this,request);
397 _handler.handle(target,baseRequest, request, response);
398 return;
399 }
400
401
402
403 if (!isPathGzipable(path))
404 {
405 LOG.debug("{} excluded by path {}",this,request);
406 _handler.handle(target,baseRequest, request, response);
407 return;
408 }
409
410
411 String mimeType = context==null?null:context.getMimeType(path);
412 if (mimeType!=null)
413 {
414 mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
415 if (!isMimeTypeGzipable(mimeType))
416 {
417 LOG.debug("{} excluded by path suffix mime type {}",this,request);
418
419 _handler.handle(target,baseRequest, request, response);
420 return;
421 }
422 }
423
424 if (_checkGzExists && context!=null)
425 {
426 String realpath=request.getServletContext().getRealPath(path);
427 if (realpath!=null)
428 {
429 File gz=new File(realpath+".gz");
430 if (gz.exists())
431 {
432 LOG.debug("{} gzip exists {}",this,request);
433
434 _handler.handle(target,baseRequest, request, response);
435 return;
436 }
437 }
438 }
439
440
441 String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
442 if (etag!=null)
443 {
444 int i=etag.indexOf(ETAG_GZIP_QUOTE);
445 if (i>0)
446 {
447 while (i>=0)
448 {
449 etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length());
450 i=etag.indexOf(ETAG_GZIP_QUOTE,i);
451 }
452 baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag));
453 }
454 }
455
456
457 out.setInterceptor(new GzipHttpOutputInterceptor(this,_vary,baseRequest.getHttpChannel(),out.getInterceptor(),_syncFlush));
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 }