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