View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.http.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.zip.DeflaterOutputStream;
27  
28  import javax.servlet.ServletOutputStream;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.eclipse.jetty.util.ByteArrayOutputStream2;
33  
34  /* ------------------------------------------------------------ */
35  /**
36   * Skeletal implementation of a CompressedStream. This class adds compression features to a ServletOutputStream and takes care of setting response headers, etc.
37   * Major work and configuration is done here. Subclasses using different kinds of compression only have to implement the abstract methods doCompress() and
38   * setContentEncoding() using the desired compression and setting the appropriate Content-Encoding header string.
39   */
40  public abstract class AbstractCompressedStream extends ServletOutputStream 
41  {
42      private final String _encoding;
43      protected HttpServletRequest _request;
44      protected HttpServletResponse _response;
45      protected OutputStream _out;
46      protected ByteArrayOutputStream2 _bOut;
47      protected DeflaterOutputStream _compressedOutputStream;
48      protected boolean _closed;
49      protected int _bufferSize;
50      protected int _minCompressSize;
51      protected long _contentLength;
52      protected boolean _doNotCompress;
53  
54      /**
55       * Instantiates a new compressed stream.
56       * 
57       * @param request
58       *            the request
59       * @param response
60       *            the response
61       * @param contentLength
62       *            the content length
63       * @param bufferSize
64       *            the buffer size
65       * @param minCompressSize
66       *            the min compress size
67       * @throws IOException
68       *             Signals that an I/O exception has occurred.
69       */
70      public AbstractCompressedStream(String encoding,HttpServletRequest request, HttpServletResponse response, long contentLength, int bufferSize, int minCompressSize)
71              throws IOException
72      {
73          _encoding=encoding;
74          _request = request;
75          _response = response;
76          _contentLength = contentLength;
77          _bufferSize = bufferSize;
78          _minCompressSize = minCompressSize;
79          if (minCompressSize == 0)
80              doCompress();
81      }
82  
83      /**
84       * Reset buffer.
85       */
86      public void resetBuffer()
87      {
88          if (_response.isCommitted())
89              throw new IllegalStateException("Committed");
90          _closed = false;
91          _out = null;
92          _bOut = null;
93          if (_compressedOutputStream != null)
94              _response.setHeader("Content-Encoding",null);
95          _compressedOutputStream = null;
96          _doNotCompress = false;
97      }
98  
99      /**
100      * Sets the content length.
101      * 
102      * @param length
103      *            the new content length
104      */
105     public void setContentLength(long length)
106     {
107         _contentLength = length;
108         if (_doNotCompress && length >= 0)
109         {
110             if (_contentLength < Integer.MAX_VALUE)
111                 _response.setContentLength((int)_contentLength);
112             else
113                 _response.setHeader("Content-Length",Long.toString(_contentLength));
114         }
115     }
116 
117     /* ------------------------------------------------------------ */
118     /**
119      * @see java.io.OutputStream#flush()
120      */
121     @Override
122     public void flush() throws IOException
123     {
124         if (_out == null || _bOut != null)
125         {
126             if (_contentLength > 0 && _contentLength < _minCompressSize)
127                 doNotCompress();
128             else
129                 doCompress();
130         }
131 
132         _out.flush();
133     }
134 
135     /* ------------------------------------------------------------ */
136     /**
137      * @see java.io.OutputStream#close()
138      */
139     @Override
140     public void close() throws IOException
141     {
142         if (_closed)
143             return;
144 
145         if (_request.getAttribute("javax.servlet.include.request_uri") != null)
146             flush();
147         else
148         {
149             if (_bOut != null)
150             {
151                 if (_contentLength < 0)
152                     _contentLength = _bOut.getCount();
153                 if (_contentLength < _minCompressSize)
154                     doNotCompress();
155                 else
156                     doCompress();
157             }
158             else if (_out == null)
159             {
160                 doNotCompress();
161             }
162 
163             if (_compressedOutputStream != null)
164                 _compressedOutputStream.close();
165             else
166                 _out.close();
167             _closed = true;
168         }
169     }
170 
171     /**
172      * Finish.
173      * 
174      * @throws IOException
175      *             Signals that an I/O exception has occurred.
176      */
177     public void finish() throws IOException
178     {
179         if (!_closed)
180         {
181             if (_out == null || _bOut != null)
182             {
183                 if (_contentLength > 0 && _contentLength < _minCompressSize)
184                     doNotCompress();
185                 else
186                     doCompress();
187             }
188 
189             if (_compressedOutputStream != null && !_closed)
190             {
191                 _closed = true;
192                 _compressedOutputStream.close();
193             }
194         }
195     }
196 
197     /* ------------------------------------------------------------ */
198     /**
199      * @see java.io.OutputStream#write(int)
200      */
201     @Override
202     public void write(int b) throws IOException
203     {
204         checkOut(1);
205         _out.write(b);
206     }
207 
208     /* ------------------------------------------------------------ */
209     /**
210      * @see java.io.OutputStream#write(byte[])
211      */
212     @Override
213     public void write(byte b[]) throws IOException
214     {
215         checkOut(b.length);
216         _out.write(b);
217     }
218 
219     /* ------------------------------------------------------------ */
220     /**
221      * @see java.io.OutputStream#write(byte[], int, int)
222      */
223     @Override
224     public void write(byte b[], int off, int len) throws IOException
225     {
226         checkOut(len);
227         _out.write(b,off,len);
228     }
229     
230     /**
231      * Do compress.
232      *
233      * @throws IOException Signals that an I/O exception has occurred.
234      */
235     public void doCompress() throws IOException
236     {
237         if (_compressedOutputStream==null) 
238         {
239             if (_response.isCommitted())
240                 throw new IllegalStateException();
241             
242             setHeader("Content-Encoding", _encoding);            
243             if (_response.containsHeader("Content-Encoding"))
244             {
245                 _out=_compressedOutputStream=createStream();
246 
247                 if (_bOut!=null)
248                 {
249                     _out.write(_bOut.getBuf(),0,_bOut.getCount());
250                     _bOut=null;
251                 }
252             }
253             else 
254                 doNotCompress();
255         }
256     }
257 
258     /**
259      * Do not compress.
260      * 
261      * @throws IOException
262      *             Signals that an I/O exception has occurred.
263      */
264     public void doNotCompress() throws IOException
265     {
266         if (_compressedOutputStream != null)
267             throw new IllegalStateException("Compressed output stream is already assigned.");
268         if (_out == null || _bOut != null)
269         {
270             _doNotCompress = true;
271 
272             _out = _response.getOutputStream();
273             setContentLength(_contentLength);
274 
275             if (_bOut != null)
276                 _out.write(_bOut.getBuf(),0,_bOut.getCount());
277             _bOut = null;
278         }
279     }
280 
281     /**
282      * Check out.
283      * 
284      * @param length
285      *            the length
286      * @throws IOException
287      *             Signals that an I/O exception has occurred.
288      */
289     private void checkOut(int length) throws IOException
290     {
291         if (_closed)
292             throw new IOException("CLOSED");
293 
294         if (_out == null)
295         {
296             if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize))
297                 doNotCompress();
298             else if (length > _minCompressSize)
299                 doCompress();
300             else
301                 _out = _bOut = new ByteArrayOutputStream2(_bufferSize);
302         }
303         else if (_bOut != null)
304         {
305             if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize))
306                 doNotCompress();
307             else if (length >= (_bOut.getBuf().length - _bOut.getCount()))
308                 doCompress();
309         }
310     }
311 
312     /**
313      * @see org.eclipse.jetty.http.gzip.CompressedStream#getOutputStream()
314      */
315     public OutputStream getOutputStream()
316     {
317         return _out;
318     }
319 
320     /**
321      * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
322      */
323     public boolean isClosed()
324     {
325         return _closed;
326     }
327     
328     /**
329      * Allows derived implementations to replace PrintWriter implementation.
330      */
331     protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
332     {
333         return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
334     }
335 
336     protected void setHeader(String name,String value)
337     {
338         _response.setHeader(name, value);
339     }
340     
341     /**
342      * Create the stream fitting to the underlying compression type.
343      * 
344      * @throws IOException
345      *             Signals that an I/O exception has occurred.
346      */
347     protected abstract DeflaterOutputStream createStream() throws IOException;
348 
349 }