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