View Javadoc

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