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