View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.servlets.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 final String _vary;
44      protected final CompressedResponseWrapper _wrapper;
45      protected final HttpServletResponse _response;
46      protected OutputStream _out;
47      protected ByteArrayOutputStream2 _bOut;
48      protected DeflaterOutputStream _compressedOutputStream;
49      protected boolean _closed;
50      protected boolean _doNotCompress;
51  
52      /**
53       * Instantiates a new compressed stream.
54       *
55       */
56      public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper,String vary)
57              throws IOException
58      {
59          _encoding=encoding;
60          _wrapper = wrapper;
61          _response = (HttpServletResponse)wrapper.getResponse();
62          _vary=vary;
63          
64          if (_wrapper.getMinCompressSize()==0)
65              doCompress();
66      }
67  
68      /**
69       * Reset buffer.
70       */
71      public void resetBuffer()
72      {
73          if (_response.isCommitted())
74              throw new IllegalStateException("Committed");
75          _closed = false;
76          _out = null;
77          _bOut = null;
78          if (_compressedOutputStream != null)
79              _response.setHeader("Content-Encoding",null);
80          _compressedOutputStream = null;
81          _doNotCompress = false;
82      }
83  
84      /* ------------------------------------------------------------ */
85      public void setContentLength()
86      {
87          if (_doNotCompress)
88          {
89              long length=_wrapper.getContentLength();
90              if (length>=0)
91              {
92                  if (length < Integer.MAX_VALUE)
93                      _response.setContentLength((int)length);
94                  else
95                      _response.setHeader("Content-Length",Long.toString(length));
96              }
97          }
98      }
99  
100     /* ------------------------------------------------------------ */
101     /**
102      * @see java.io.OutputStream#flush()
103      */
104     @Override
105     public void flush() throws IOException
106     {
107         if (_out == null || _bOut != null)
108         {
109             long length=_wrapper.getContentLength();
110             if (length > 0 && length < _wrapper.getMinCompressSize())
111                 doNotCompress(false);
112             else
113                 doCompress();
114         }
115 
116         _out.flush();
117     }
118 
119     /* ------------------------------------------------------------ */
120     /**
121      * @see java.io.OutputStream#close()
122      */
123     @Override
124     public void close() throws IOException
125     {
126         if (_closed)
127             return;
128 
129         if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
130             flush();
131         else
132         {
133             if (_bOut != null)
134             {
135                 long length=_wrapper.getContentLength();
136                 if (length < 0)
137                 {
138                     length = _bOut.getCount();
139                     _wrapper.setContentLength(length);
140                 }
141                 if (length < _wrapper.getMinCompressSize())
142                     doNotCompress(false);
143                 else
144                     doCompress();
145             }
146             else if (_out == null)
147             {
148                 // No output
149                 doNotCompress(false);
150             }
151 
152             if (_compressedOutputStream != null)
153                 _compressedOutputStream.close();
154             else
155                 _out.close();
156             _closed = true;
157         }
158     }
159 
160     /**
161      * Finish.
162      *
163      * @throws IOException
164      *             Signals that an I/O exception has occurred.
165      */
166     public void finish() throws IOException
167     {
168         if (!_closed)
169         {
170             if (_out == null || _bOut != null)
171             {
172                 long length=_wrapper.getContentLength();
173                 if (length > 0 && length < _wrapper.getMinCompressSize())
174                     doNotCompress(false);
175                 else
176                     doCompress();
177             }
178 
179             if (_compressedOutputStream != null && !_closed)
180             {
181                 _closed = true;
182                 _compressedOutputStream.close();
183             }
184         }
185     }
186 
187     /* ------------------------------------------------------------ */
188     /**
189      * @see java.io.OutputStream#write(int)
190      */
191     @Override
192     public void write(int b) throws IOException
193     {
194         checkOut(1);
195         _out.write(b);
196     }
197 
198     /* ------------------------------------------------------------ */
199     /**
200      * @see java.io.OutputStream#write(byte[])
201      */
202     @Override
203     public void write(byte b[]) throws IOException
204     {
205         checkOut(b.length);
206         _out.write(b);
207     }
208 
209     /* ------------------------------------------------------------ */
210     /**
211      * @see java.io.OutputStream#write(byte[], int, int)
212      */
213     @Override
214     public void write(byte b[], int off, int len) throws IOException
215     {
216         checkOut(len);
217         _out.write(b,off,len);
218     }
219 
220     /**
221      * Do compress.
222      *
223      * @throws IOException Signals that an I/O exception has occurred.
224      */
225     public void doCompress() throws IOException
226     {
227         if (_compressedOutputStream==null)
228         {
229             if (_response.isCommitted())
230                 throw new IllegalStateException();
231 
232             if (_encoding!=null)
233             {
234             setHeader("Content-Encoding", _encoding);
235                 if (_response.containsHeader("Content-Encoding"))
236                 {
237                     setHeader("Vary",_vary);
238                     _out=_compressedOutputStream=createStream();
239                     if (_out!=null)
240                     {
241                         if (_bOut!=null)
242                         {
243                             _out.write(_bOut.getBuf(),0,_bOut.getCount());
244                             _bOut=null;
245                         }
246 
247                         String etag=_wrapper.getETag();
248                         if (etag!=null)
249                             setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"');
250                         return;
251                     }
252                 }
253             }
254             
255             doNotCompress(true); // Send vary as it could have been compressed if encoding was present
256         }
257     }
258 
259     /**
260      * Do not compress.
261      *
262      * @throws IOException
263      *             Signals that an I/O exception has occurred.
264      */
265     public void doNotCompress(boolean sendVary) throws IOException
266     {
267         if (_compressedOutputStream != null)
268             throw new IllegalStateException("Compressed output stream is already assigned.");
269         if (_out == null || _bOut != null)
270         {
271             if (sendVary)
272                 setHeader("Vary",_vary);
273             if (_wrapper.getETag()!=null)
274                 setHeader("ETag",_wrapper.getETag());
275                 
276             _doNotCompress = true;
277 
278             _out = _response.getOutputStream();
279             setContentLength();
280 
281             if (_bOut != null)
282                 _out.write(_bOut.getBuf(),0,_bOut.getCount());
283             _bOut = null;
284         }
285     }
286 
287     /**
288      * Check out.
289      *
290      * @param lengthToWrite
291      *            the length
292      * @throws IOException
293      *             Signals that an I/O exception has occurred.
294      */
295     private void checkOut(int lengthToWrite) throws IOException
296     {
297         if (_closed)
298             throw new IOException("CLOSED");
299 
300         if (_out == null)
301         {
302             long length=_wrapper.getContentLength();
303             if (_response.isCommitted() || (length >= 0 && length < _wrapper.getMinCompressSize()))
304                 doNotCompress(false);
305             else if (lengthToWrite > _wrapper.getMinCompressSize())
306                 doCompress();
307             else
308                 _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
309         }
310         else if (_bOut != null)
311         {
312             long length=_wrapper.getContentLength();
313             if (_response.isCommitted() || (length >= 0 && length < _wrapper.getMinCompressSize()))
314                 doNotCompress(false);
315             else if (lengthToWrite >= (_bOut.getBuf().length - _bOut.getCount()))
316                 doCompress();
317         }
318     }
319 
320     /**
321      * @see org.eclipse.jetty.http.gzip.CompressedStream#getOutputStream()
322      */
323     public OutputStream getOutputStream()
324     {
325         return _out;
326     }
327 
328     /**
329      * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
330      */
331     public boolean isClosed()
332     {
333         return _closed;
334     }
335 
336     /**
337      * Allows derived implementations to replace PrintWriter implementation.
338      */
339     protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
340     {
341         return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
342     }
343 
344     protected void setHeader(String name,String value)
345     {
346         _response.setHeader(name, value);
347     }
348 
349     /**
350      * Create the stream fitting to the underlying compression type.
351      *
352      * @throws IOException
353      *             Signals that an I/O exception has occurred.
354      */
355     protected abstract DeflaterOutputStream createStream() throws IOException;
356 
357 }