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