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             
239             if (_response.containsHeader("Content-Encoding"))
240             {
241                 _out=_compressedOutputStream=createStream();
242 
243                 if (_bOut!=null)
244                 {
245                     _out.write(_bOut.getBuf(),0,_bOut.getCount());
246                     _bOut=null;
247                 }
248             }
249             else 
250                 doNotCompress();
251         }
252     }
253 
254     /**
255      * Do not compress.
256      * 
257      * @throws IOException
258      *             Signals that an I/O exception has occurred.
259      */
260     public void doNotCompress() throws IOException
261     {
262         if (_compressedOutputStream != null)
263             throw new IllegalStateException("Compressed output stream is already assigned.");
264         if (_out == null || _bOut != null)
265         {
266             _doNotCompress = true;
267 
268             _out = _response.getOutputStream();
269             setContentLength(_contentLength);
270 
271             if (_bOut != null)
272                 _out.write(_bOut.getBuf(),0,_bOut.getCount());
273             _bOut = null;
274         }
275     }
276 
277     /**
278      * Check out.
279      * 
280      * @param length
281      *            the length
282      * @throws IOException
283      *             Signals that an I/O exception has occurred.
284      */
285     private void checkOut(int length) throws IOException
286     {
287         if (_closed)
288             throw new IOException("CLOSED");
289 
290         if (_out == null)
291         {
292             if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize))
293                 doNotCompress();
294             else if (length > _minCompressSize)
295                 doCompress();
296             else
297                 _out = _bOut = new ByteArrayOutputStream2(_bufferSize);
298         }
299         else if (_bOut != null)
300         {
301             if (_response.isCommitted() || (_contentLength >= 0 && _contentLength < _minCompressSize))
302                 doNotCompress();
303             else if (length >= (_bOut.getBuf().length - _bOut.getCount()))
304                 doCompress();
305         }
306     }
307 
308     /**
309      * @see org.eclipse.jetty.http.gzip.CompressedStream#getOutputStream()
310      */
311     public OutputStream getOutputStream()
312     {
313         return _out;
314     }
315 
316     /**
317      * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
318      */
319     public boolean isClosed()
320     {
321         return _closed;
322     }
323     
324     /**
325      * Allows derived implementations to replace PrintWriter implementation.
326      */
327     protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
328     {
329         return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
330     }
331 
332     protected void setHeader(String name,String value)
333     {
334         _response.setHeader(name, value);
335     }
336     
337     /**
338      * Create the stream fitting to the underlying compression type.
339      * 
340      * @throws IOException
341      *             Signals that an I/O exception has occurred.
342      */
343     protected abstract DeflaterOutputStream createStream() throws IOException;
344 
345 }