1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jetty.servlets;
14
15 import java.io.IOException;
16 import java.io.OutputStream;
17 import java.io.OutputStreamWriter;
18 import java.io.PrintWriter;
19 import java.io.UnsupportedEncodingException;
20 import java.util.HashSet;
21 import java.util.Set;
22 import java.util.StringTokenizer;
23 import java.util.zip.GZIPOutputStream;
24
25 import javax.servlet.FilterChain;
26 import javax.servlet.FilterConfig;
27 import javax.servlet.ServletException;
28 import javax.servlet.ServletOutputStream;
29 import javax.servlet.ServletRequest;
30 import javax.servlet.ServletResponse;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 import javax.servlet.http.HttpServletResponseWrapper;
34
35 import org.eclipse.jetty.continuation.Continuation;
36 import org.eclipse.jetty.continuation.ContinuationListener;
37 import org.eclipse.jetty.continuation.ContinuationSupport;
38 import org.eclipse.jetty.http.HttpMethods;
39 import org.eclipse.jetty.util.ByteArrayOutputStream2;
40 import org.eclipse.jetty.util.StringUtil;
41 import org.eclipse.jetty.util.log.Log;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 public class GzipFilter extends UserAgentFilter
67 {
68 protected Set _mimeTypes;
69 protected int _bufferSize=8192;
70 protected int _minGzipSize=256;
71 protected Set _excluded;
72
73 public void init(FilterConfig filterConfig) throws ServletException
74 {
75 super.init(filterConfig);
76
77 String tmp=filterConfig.getInitParameter("bufferSize");
78 if (tmp!=null)
79 _bufferSize=Integer.parseInt(tmp);
80
81 tmp=filterConfig.getInitParameter("minGzipSize");
82 if (tmp!=null)
83 _minGzipSize=Integer.parseInt(tmp);
84
85 tmp=filterConfig.getInitParameter("mimeTypes");
86 if (tmp!=null)
87 {
88 _mimeTypes=new HashSet();
89 StringTokenizer tok = new StringTokenizer(tmp,",",false);
90 while (tok.hasMoreTokens())
91 _mimeTypes.add(tok.nextToken());
92 }
93
94 tmp=filterConfig.getInitParameter("excludedAgents");
95 if (tmp!=null)
96 {
97 _excluded=new HashSet();
98 StringTokenizer tok = new StringTokenizer(tmp,",",false);
99 while (tok.hasMoreTokens())
100 _excluded.add(tok.nextToken());
101 }
102 }
103
104 public void destroy()
105 {
106 }
107
108 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
109 throws IOException, ServletException
110 {
111 HttpServletRequest request=(HttpServletRequest)req;
112 HttpServletResponse response=(HttpServletResponse)res;
113
114 String ae = request.getHeader("accept-encoding");
115 if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")
116 && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod()))
117 {
118 if (_excluded!=null)
119 {
120 String ua=getUserAgent(request);
121 if (_excluded.contains(ua))
122 {
123 super.doFilter(request,response,chain);
124 return;
125 }
126 }
127
128 final GZIPResponseWrapper wrappedResponse=newGZIPResponseWrapper(request,response);
129
130 boolean exceptional=true;
131 try
132 {
133 super.doFilter(request,wrappedResponse,chain);
134 exceptional=false;
135 }
136 finally
137 {
138 Continuation continuation = ContinuationSupport.getContinuation(request);
139 if (continuation.isSuspended() && continuation.isResponseWrapped())
140 {
141 continuation.addContinuationListener(new ContinuationListener()
142 {
143 public void onComplete(Continuation continuation)
144 {
145 try
146 {
147 wrappedResponse.finish();
148 }
149 catch(IOException e)
150 {
151 Log.warn(e);
152 }
153 }
154
155 public void onTimeout(Continuation continuation)
156 {}
157 });
158 }
159 else if (exceptional && !response.isCommitted())
160 {
161 wrappedResponse.resetBuffer();
162 wrappedResponse.noGzip();
163 }
164 else
165 wrappedResponse.finish();
166 }
167 }
168 else
169 {
170 super.doFilter(request,response,chain);
171 }
172 }
173
174 protected GZIPResponseWrapper newGZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response)
175 {
176 return new GZIPResponseWrapper(request,response);
177 }
178
179
180
181
182 protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
183 {
184 return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
185 }
186
187 public class GZIPResponseWrapper extends HttpServletResponseWrapper
188 {
189 HttpServletRequest _request;
190 boolean _noGzip;
191 PrintWriter _writer;
192 GzipStream _gzStream;
193 long _contentLength=-1;
194
195 public GZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response)
196 {
197 super(response);
198 _request=request;
199 }
200
201 public void setContentType(String ct)
202 {
203 super.setContentType(ct);
204
205 if (ct!=null)
206 {
207 int colon=ct.indexOf(";");
208 if (colon>0)
209 ct=ct.substring(0,colon);
210 }
211
212 if ((_gzStream==null || _gzStream._out==null) &&
213 (_mimeTypes==null && "application/gzip".equalsIgnoreCase(ct) ||
214 _mimeTypes!=null && (ct==null||!_mimeTypes.contains(StringUtil.asciiToLowerCase(ct)))))
215 {
216 noGzip();
217 }
218 }
219
220 public void setStatus(int sc, String sm)
221 {
222 super.setStatus(sc,sm);
223 if (sc<200||sc>=300)
224 noGzip();
225 }
226
227 public void setStatus(int sc)
228 {
229 super.setStatus(sc);
230 if (sc<200||sc>=300)
231 noGzip();
232 }
233
234 public void setContentLength(int length)
235 {
236 _contentLength=length;
237 if (_gzStream!=null)
238 _gzStream.setContentLength(length);
239 }
240
241 public void addHeader(String name, String value)
242 {
243 if ("content-length".equalsIgnoreCase(name))
244 {
245 _contentLength=Long.parseLong(value);
246 if (_gzStream!=null)
247 _gzStream.setContentLength(_contentLength);
248 }
249 else if ("content-type".equalsIgnoreCase(name))
250 {
251 setContentType(value);
252 }
253 else if ("content-encoding".equalsIgnoreCase(name))
254 {
255 super.addHeader(name,value);
256 if (!isCommitted())
257 {
258 noGzip();
259 }
260 }
261 else
262 super.addHeader(name,value);
263 }
264
265 public void setHeader(String name, String value)
266 {
267 if ("content-length".equalsIgnoreCase(name))
268 {
269 _contentLength=Long.parseLong(value);
270 if (_gzStream!=null)
271 _gzStream.setContentLength(_contentLength);
272 }
273 else if ("content-type".equalsIgnoreCase(name))
274 {
275 setContentType(value);
276 }
277 else if ("content-encoding".equalsIgnoreCase(name))
278 {
279 super.setHeader(name,value);
280 if (!isCommitted())
281 {
282 noGzip();
283 }
284 }
285 else
286 super.setHeader(name,value);
287 }
288
289 public void setIntHeader(String name, int value)
290 {
291 if ("content-length".equalsIgnoreCase(name))
292 {
293 _contentLength=value;
294 if (_gzStream!=null)
295 _gzStream.setContentLength(_contentLength);
296 }
297 else
298 super.setIntHeader(name,value);
299 }
300
301 public void flushBuffer() throws IOException
302 {
303 if (_writer!=null)
304 _writer.flush();
305 if (_gzStream!=null)
306 _gzStream.finish();
307 else
308 getResponse().flushBuffer();
309 }
310
311 public void reset()
312 {
313 super.reset();
314 if (_gzStream!=null)
315 _gzStream.resetBuffer();
316 _writer=null;
317 _gzStream=null;
318 _noGzip=false;
319 _contentLength=-1;
320 }
321
322 public void resetBuffer()
323 {
324 super.resetBuffer();
325 if (_gzStream!=null)
326 _gzStream.resetBuffer();
327 _writer=null;
328 _gzStream=null;
329 }
330
331 public void sendError(int sc, String msg) throws IOException
332 {
333 resetBuffer();
334 super.sendError(sc,msg);
335 }
336
337 public void sendError(int sc) throws IOException
338 {
339 resetBuffer();
340 super.sendError(sc);
341 }
342
343 public void sendRedirect(String location) throws IOException
344 {
345 resetBuffer();
346 super.sendRedirect(location);
347 }
348
349 public ServletOutputStream getOutputStream() throws IOException
350 {
351 if (_gzStream==null)
352 {
353 if (getResponse().isCommitted() || _noGzip)
354 return getResponse().getOutputStream();
355
356 _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
357 }
358 else if (_writer!=null)
359 throw new IllegalStateException("getWriter() called");
360
361 return _gzStream;
362 }
363
364 public PrintWriter getWriter() throws IOException
365 {
366 if (_writer==null)
367 {
368 if (_gzStream!=null)
369 throw new IllegalStateException("getOutputStream() called");
370
371 if (getResponse().isCommitted() || _noGzip)
372 return getResponse().getWriter();
373
374 _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
375 _writer=newWriter(_gzStream,getCharacterEncoding());
376 }
377 return _writer;
378 }
379
380 void noGzip()
381 {
382 _noGzip=true;
383 if (_gzStream!=null)
384 {
385 try
386 {
387 _gzStream.doNotGzip();
388 }
389 catch (IOException e)
390 {
391 throw new IllegalStateException(e);
392 }
393 }
394 }
395
396 void finish() throws IOException
397 {
398 if (_writer!=null && !_gzStream._closed)
399 _writer.flush();
400 if (_gzStream!=null)
401 _gzStream.finish();
402 }
403
404 protected GzipStream newGzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException
405 {
406 return new GzipStream(request,response,contentLength,bufferSize,minGzipSize);
407 }
408 }
409
410
411 public static class GzipStream extends ServletOutputStream
412 {
413 protected HttpServletRequest _request;
414 protected HttpServletResponse _response;
415 protected OutputStream _out;
416 protected ByteArrayOutputStream2 _bOut;
417 protected GZIPOutputStream _gzOut;
418 protected boolean _closed;
419 protected int _bufferSize;
420 protected int _minGzipSize;
421 protected long _contentLength;
422
423 public GzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException
424 {
425 _request=request;
426 _response=response;
427 _contentLength=contentLength;
428 _bufferSize=bufferSize;
429 _minGzipSize=minGzipSize;
430 if (minGzipSize==0)
431 doGzip();
432 }
433
434 public void resetBuffer()
435 {
436 if (_response.isCommitted())
437 throw new IllegalStateException("Committed");
438 _closed=false;
439 _out=null;
440 _bOut=null;
441 if (_gzOut!=null)
442 _response.setHeader("Content-Encoding",null);
443 _gzOut=null;
444 }
445
446 public void setContentLength(long length)
447 {
448 _contentLength=length;
449 }
450
451 public void flush() throws IOException
452 {
453 if (_out==null || _bOut!=null)
454 {
455 if (_contentLength>0 && _contentLength<_minGzipSize)
456 doNotGzip();
457 else
458 doGzip();
459 }
460
461 _out.flush();
462 }
463
464 public void close() throws IOException
465 {
466 if (_closed)
467 return;
468
469 if (_request.getAttribute("javax.servlet.include.request_uri")!=null)
470 flush();
471 else
472 {
473 if (_bOut!=null)
474 {
475 if (_contentLength<0)
476 _contentLength=_bOut.getCount();
477 if (_contentLength<_minGzipSize)
478 doNotGzip();
479 else
480 doGzip();
481 }
482 else if (_out==null)
483 {
484 doNotGzip();
485 }
486
487 if (_gzOut!=null)
488 _gzOut.close();
489 else
490 _out.close();
491 _closed=true;
492 }
493 }
494
495 public void finish() throws IOException
496 {
497 if (!_closed)
498 {
499 if (_out==null || _bOut!=null)
500 {
501 if (_contentLength>0 && _contentLength<_minGzipSize)
502 doNotGzip();
503 else
504 doGzip();
505 }
506
507 if (_gzOut!=null && !_closed)
508 {
509 _closed=true;
510 _gzOut.close();
511 }
512 }
513 }
514
515 public void write(int b) throws IOException
516 {
517 checkOut(1);
518 _out.write(b);
519 }
520
521 public void write(byte b[]) throws IOException
522 {
523 checkOut(b.length);
524 _out.write(b);
525 }
526
527 public void write(byte b[], int off, int len) throws IOException
528 {
529 checkOut(len);
530 _out.write(b,off,len);
531 }
532
533 protected boolean setContentEncodingGzip()
534 {
535 _response.setHeader("Content-Encoding", "gzip");
536 return _response.containsHeader("Content-Encoding");
537 }
538
539 public void doGzip() throws IOException
540 {
541 if (_gzOut==null)
542 {
543 if (_response.isCommitted())
544 throw new IllegalStateException();
545
546 if (setContentEncodingGzip())
547 {
548 _out=_gzOut=new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
549
550 if (_bOut!=null)
551 {
552 _out.write(_bOut.getBuf(),0,_bOut.getCount());
553 _bOut=null;
554 }
555 }
556 else
557 doNotGzip();
558 }
559 }
560
561 public void doNotGzip() throws IOException
562 {
563 if (_gzOut!=null)
564 throw new IllegalStateException();
565 if (_out==null || _bOut!=null )
566 {
567 _out=_response.getOutputStream();
568 if (_contentLength>=0)
569 {
570 if(_contentLength<Integer.MAX_VALUE)
571 _response.setContentLength((int)_contentLength);
572 else
573 _response.setHeader("Content-Length",Long.toString(_contentLength));
574 }
575
576 if (_bOut!=null)
577 _out.write(_bOut.getBuf(),0,_bOut.getCount());
578 _bOut=null;
579 }
580 }
581
582 private void checkOut(int length) throws IOException
583 {
584 if (_closed)
585 throw new IOException("CLOSED");
586
587 if (_out==null)
588 {
589 if (_response.isCommitted() || (_contentLength>=0 && _contentLength<_minGzipSize))
590 doNotGzip();
591 else if (length>_minGzipSize)
592 doGzip();
593 else
594 _out=_bOut=new ByteArrayOutputStream2(_bufferSize);
595 }
596 else if (_bOut!=null)
597 {
598 if (_response.isCommitted() || (_contentLength>=0 && _contentLength<_minGzipSize))
599 doNotGzip();
600 else if (length>=(_bOut.getBuf().length -_bOut.getCount()))
601 doGzip();
602 }
603 }
604 }
605
606 }