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
65
66 public class GzipFilter extends UserAgentFilter
67 {
68 protected Set _mimeTypes;
69 protected int _bufferSize=8192;
70 protected int _minGzipSize=1;
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 {
117 if (_excluded!=null)
118 {
119 String ua=getUserAgent(request);
120 if (_excluded.contains(ua))
121 {
122 super.doFilter(request,response,chain);
123 return;
124 }
125 }
126
127 final GZIPResponseWrapper wrappedResponse=newGZIPResponseWrapper(request,response);
128
129 boolean exceptional=true;
130 try
131 {
132 super.doFilter(request,wrappedResponse,chain);
133 exceptional=false;
134 }
135 finally
136 {
137 Continuation continuation = ContinuationSupport.getContinuation(request,response);
138 if (continuation.isSuspended() && continuation.isResponseWrapped())
139 {
140 continuation.addContinuationListener(new ContinuationListener()
141 {
142 public void onComplete(Continuation continuation)
143 {
144 try
145 {
146 wrappedResponse.finish();
147 }
148 catch(IOException e)
149 {
150 Log.warn(e);
151 }
152 }
153
154 public void onTimeout(Continuation continuation)
155 {}
156 });
157 }
158 else if (exceptional && !response.isCommitted())
159 {
160 wrappedResponse.resetBuffer();
161 wrappedResponse.noGzip();
162 }
163 else
164 wrappedResponse.finish();
165 }
166 }
167 else
168 {
169 super.doFilter(request,response,chain);
170 }
171 }
172
173 protected GZIPResponseWrapper newGZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response)
174 {
175 return new GZIPResponseWrapper(request,response);
176 }
177
178 public class GZIPResponseWrapper extends HttpServletResponseWrapper
179 {
180 HttpServletRequest _request;
181 boolean _noGzip;
182 PrintWriter _writer;
183 GzipStream _gzStream;
184 long _contentLength=-1;
185
186 public GZIPResponseWrapper(HttpServletRequest request, HttpServletResponse response)
187 {
188 super(response);
189 _request=request;
190 }
191
192 public void setContentType(String ct)
193 {
194 super.setContentType(ct);
195 int colon=ct.indexOf(";");
196 if (colon>0)
197 ct=ct.substring(0,colon);
198
199 if ((_gzStream==null || _gzStream._out==null) &&
200 (_mimeTypes==null && "application/gzip".equalsIgnoreCase(ct) ||
201 _mimeTypes!=null && !_mimeTypes.contains(StringUtil.asciiToLowerCase(ct))))
202 {
203 noGzip();
204 }
205 }
206
207 public void setStatus(int sc, String sm)
208 {
209 super.setStatus(sc,sm);
210 if (sc<200||sc>=300)
211 noGzip();
212 }
213
214 public void setStatus(int sc)
215 {
216 super.setStatus(sc);
217 if (sc<200||sc>=300)
218 noGzip();
219 }
220
221 public void setContentLength(int length)
222 {
223 _contentLength=length;
224 if (_gzStream!=null)
225 _gzStream.setContentLength(length);
226 }
227
228 public void addHeader(String name, String value)
229 {
230 if ("content-length".equalsIgnoreCase(name))
231 {
232 _contentLength=Long.parseLong(value);
233 if (_gzStream!=null)
234 _gzStream.setContentLength(_contentLength);
235 }
236 else if ("content-type".equalsIgnoreCase(name))
237 {
238 setContentType(value);
239 }
240 else if ("content-encoding".equalsIgnoreCase(name))
241 {
242 super.addHeader(name,value);
243 if (!isCommitted())
244 {
245 noGzip();
246 }
247 }
248 else
249 super.addHeader(name,value);
250 }
251
252 public void setHeader(String name, String value)
253 {
254 if ("content-length".equalsIgnoreCase(name))
255 {
256 _contentLength=Long.parseLong(value);
257 if (_gzStream!=null)
258 _gzStream.setContentLength(_contentLength);
259 }
260 else if ("content-type".equalsIgnoreCase(name))
261 {
262 setContentType(value);
263 }
264 else if ("content-encoding".equalsIgnoreCase(name))
265 {
266 super.setHeader(name,value);
267 if (!isCommitted())
268 {
269 noGzip();
270 }
271 }
272 else
273 super.setHeader(name,value);
274 }
275
276 public void setIntHeader(String name, int value)
277 {
278 if ("content-length".equalsIgnoreCase(name))
279 {
280 _contentLength=value;
281 if (_gzStream!=null)
282 _gzStream.setContentLength(_contentLength);
283 }
284 else
285 super.setIntHeader(name,value);
286 }
287
288 public void flushBuffer() throws IOException
289 {
290 if (_writer!=null)
291 _writer.flush();
292 if (_gzStream!=null)
293 _gzStream.finish();
294 else
295 getResponse().flushBuffer();
296 }
297
298 public void reset()
299 {
300 super.reset();
301 if (_gzStream!=null)
302 _gzStream.resetBuffer();
303 _writer=null;
304 _gzStream=null;
305 _noGzip=false;
306 _contentLength=-1;
307 }
308
309 public void resetBuffer()
310 {
311 super.resetBuffer();
312 if (_gzStream!=null)
313 _gzStream.resetBuffer();
314 _writer=null;
315 _gzStream=null;
316 }
317
318 public void sendError(int sc, String msg) throws IOException
319 {
320 resetBuffer();
321 super.sendError(sc,msg);
322 }
323
324 public void sendError(int sc) throws IOException
325 {
326 resetBuffer();
327 super.sendError(sc);
328 }
329
330 public void sendRedirect(String location) throws IOException
331 {
332 resetBuffer();
333 super.sendRedirect(location);
334 }
335
336 public ServletOutputStream getOutputStream() throws IOException
337 {
338 if (_gzStream==null)
339 {
340 if (getResponse().isCommitted() || _noGzip)
341 return getResponse().getOutputStream();
342
343 _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
344 }
345 else if (_writer!=null)
346 throw new IllegalStateException("getWriter() called");
347
348 return _gzStream;
349 }
350
351 public PrintWriter getWriter() throws IOException
352 {
353 if (_writer==null)
354 {
355 if (_gzStream!=null)
356 throw new IllegalStateException("getOutputStream() called");
357
358 if (getResponse().isCommitted() || _noGzip)
359 return getResponse().getWriter();
360
361 _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
362 String encoding = getCharacterEncoding();
363 _writer=encoding==null?new PrintWriter(_gzStream):new PrintWriter(new OutputStreamWriter(_gzStream,encoding));
364 }
365 return _writer;
366 }
367
368 void noGzip()
369 {
370 _noGzip=true;
371 if (_gzStream!=null)
372 {
373 try
374 {
375 _gzStream.doNotGzip();
376 }
377 catch (IOException e)
378 {
379 throw new IllegalStateException(e);
380 }
381 }
382 }
383
384 void finish() throws IOException
385 {
386 if (_writer!=null)
387 _writer.flush();
388 if (_gzStream!=null)
389 _gzStream.finish();
390 }
391
392 protected GzipStream newGzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException
393 {
394 return new GzipStream(request,response,contentLength,bufferSize,minGzipSize);
395 }
396 }
397
398
399 public static class GzipStream extends ServletOutputStream
400 {
401 protected HttpServletRequest _request;
402 protected HttpServletResponse _response;
403 protected OutputStream _out;
404 protected ByteArrayOutputStream2 _bOut;
405 protected GZIPOutputStream _gzOut;
406 protected boolean _closed;
407 protected int _bufferSize;
408 protected int _minGzipSize;
409 protected long _contentLength;
410
411 public GzipStream(HttpServletRequest request,HttpServletResponse response,long contentLength,int bufferSize, int minGzipSize) throws IOException
412 {
413 _request=request;
414 _response=response;
415 _contentLength=contentLength;
416 _bufferSize=bufferSize;
417 _minGzipSize=minGzipSize;
418 if (minGzipSize==0)
419 doGzip();
420 }
421
422 public void resetBuffer()
423 {
424 _closed=false;
425 _out=null;
426 _bOut=null;
427 if (_gzOut!=null && !_response.isCommitted())
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 (_request.getAttribute("javax.servlet.include.request_uri")!=null)
453 flush();
454 else
455 {
456 if (_bOut!=null)
457 {
458 if (_contentLength<0)
459 _contentLength=_bOut.getCount();
460 if (_contentLength<_minGzipSize)
461 doNotGzip();
462 else
463 doGzip();
464 }
465 else if (_out==null)
466 {
467 doNotGzip();
468 }
469
470 if (_gzOut!=null)
471 _gzOut.finish();
472 _out.close();
473 _closed=true;
474 }
475 }
476
477 public void finish() throws IOException
478 {
479 if (!_closed)
480 {
481 if (_out==null || _bOut!=null)
482 {
483 if (_contentLength>0 && _contentLength<_minGzipSize)
484 doNotGzip();
485 else
486 doGzip();
487 }
488
489 if (_gzOut!=null)
490 _gzOut.finish();
491 }
492 }
493
494 public void write(int b) throws IOException
495 {
496 checkOut(1);
497 _out.write(b);
498 }
499
500 public void write(byte b[]) throws IOException
501 {
502 checkOut(b.length);
503 _out.write(b);
504 }
505
506 public void write(byte b[], int off, int len) throws IOException
507 {
508 checkOut(len);
509 _out.write(b,off,len);
510 }
511
512 protected boolean setContentEncodingGzip()
513 {
514 _response.setHeader("Content-Encoding", "gzip");
515 return _response.containsHeader("Content-Encoding");
516 }
517
518 public void doGzip() throws IOException
519 {
520 if (_gzOut==null)
521 {
522 if (_response.isCommitted())
523 throw new IllegalStateException();
524
525 if (setContentEncodingGzip())
526 {
527 _out=_gzOut=new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
528
529 if (_bOut!=null)
530 {
531 _out.write(_bOut.getBuf(),0,_bOut.getCount());
532 _bOut=null;
533 }
534 }
535 else
536 doNotGzip();
537 }
538 }
539
540 public void doNotGzip() throws IOException
541 {
542 if (_gzOut!=null)
543 throw new IllegalStateException();
544 if (_out==null || _bOut!=null )
545 {
546 _out=_response.getOutputStream();
547 if (_contentLength>=0)
548 {
549 if(_contentLength<Integer.MAX_VALUE)
550 _response.setContentLength((int)_contentLength);
551 else
552 _response.setHeader("Content-Length",Long.toString(_contentLength));
553 }
554
555 if (_bOut!=null)
556 _out.write(_bOut.getBuf(),0,_bOut.getCount());
557 _bOut=null;
558 }
559 }
560
561 private void checkOut(int length) throws IOException
562 {
563 if (_closed)
564 {
565 new Throwable().printStackTrace();
566 throw new IOException("CLOSED");
567 }
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.size()-_bOut.getCount()))
583 doGzip();
584 }
585 }
586 }
587
588 }