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.WriteListener;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.eclipse.jetty.util.ByteArrayOutputStream2;
34  
35  /* ------------------------------------------------------------ */
36  /**
37   * Skeletal implementation of a CompressedStream. This class adds compression features to a ServletOutputStream and takes care of setting response headers, etc.
38   * Major work and configuration is done here. Subclasses using different kinds of compression only have to implement the abstract methods doCompress() and
39   * setContentEncoding() using the desired compression and setting the appropriate Content-Encoding header string.
40   */
41  public abstract class AbstractCompressedStream extends ServletOutputStream
42  {
43      private final String _encoding;
44      protected final String _vary;
45      protected final CompressedResponseWrapper _wrapper;
46      protected final HttpServletResponse _response;
47      protected OutputStream _out;
48      protected ByteArrayOutputStream2 _bOut;
49      protected DeflaterOutputStream _compressedOutputStream;
50      protected boolean _closed;
51      protected boolean _doNotCompress;
52  
53      /**
54       * Instantiates a new compressed stream.
55       *
56       */
57      public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper,String vary)
58              throws IOException
59      {
60          _encoding=encoding;
61          _wrapper = wrapper;
62          _response = (HttpServletResponse)wrapper.getResponse();
63          _vary=vary;
64          
65          if (_wrapper.getMinCompressSize()==0)
66              doCompress();
67      }
68  
69      /* ------------------------------------------------------------ */
70      /**
71       * Reset buffer.
72       */
73      public void resetBuffer()
74      {
75          if (_response.isCommitted() || _compressedOutputStream!=null )
76              throw new IllegalStateException("Committed");
77          _closed = false;
78          _out = null;
79          _bOut = null;
80          _doNotCompress = false;
81      }
82  
83      /* ------------------------------------------------------------ */
84      public void setBufferSize(int bufferSize)
85      {
86          if (_bOut!=null && _bOut.getBuf().length<bufferSize)
87          {
88              ByteArrayOutputStream2 b = new ByteArrayOutputStream2(bufferSize);
89              b.write(_bOut.getBuf(),0,_bOut.size());
90              _bOut=b;
91          }
92      }
93      
94      /* ------------------------------------------------------------ */
95      public void setContentLength()
96      {
97          if (_doNotCompress)
98          {
99              long length=_wrapper.getContentLength();
100             if (length>=0)
101             {
102                 if (length < Integer.MAX_VALUE)
103                     _response.setContentLength((int)length);
104                 else
105                     _response.setHeader("Content-Length",Long.toString(length));
106             }
107         }
108     }
109 
110     /* ------------------------------------------------------------ */
111     /**
112      * @see java.io.OutputStream#flush()
113      */
114     @Override
115     public void flush() throws IOException
116     {
117         if (_out == null || _bOut != null)
118         {
119             long length=_wrapper.getContentLength();
120             if (length > 0 && length < _wrapper.getMinCompressSize())
121                 doNotCompress(false);
122             else
123                 doCompress();
124         }
125 
126         _out.flush();
127     }
128 
129     /* ------------------------------------------------------------ */
130     /**
131      * @see java.io.OutputStream#close()
132      */
133     @Override
134     public void close() throws IOException
135     {
136         if (_closed)
137             return;
138 
139         if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
140             flush();
141         else
142         {
143             if (_bOut != null)
144             {
145                 long length=_wrapper.getContentLength();
146                 if (length < 0)
147                 {
148                     length = _bOut.getCount();
149                     _wrapper.setContentLength(length);
150                 }
151                 if (length < _wrapper.getMinCompressSize())
152                     doNotCompress(false);
153                 else
154                     doCompress();
155             }
156             else if (_out == null)
157             {
158                 // No output
159                 doNotCompress(false);
160             }
161 
162             if (_compressedOutputStream != null)
163                 _compressedOutputStream.close();
164             else
165                 _out.close();
166             _closed = true;
167         }
168     }
169 
170     /**
171      * Finish.
172      *
173      * @throws IOException
174      *             Signals that an I/O exception has occurred.
175      */
176     public void finish() throws IOException
177     {
178         if (!_closed)
179         {
180             if (_out == null || _bOut != null)
181             {
182                 long length=_wrapper.getContentLength();
183                 if (length<0 &&_bOut==null || length >= 0 && length < _wrapper.getMinCompressSize())
184                     doNotCompress(false);
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             if (_encoding!=null)
243             {
244                 setHeader("Content-Encoding", _encoding);
245                 if (_response.containsHeader("Content-Encoding"))
246                 {
247                     addHeader("Vary",_vary);
248                     _out=_compressedOutputStream=createStream();
249                     if (_out!=null)
250                     {
251                         if (_bOut!=null)
252                         {
253                             _out.write(_bOut.getBuf(),0,_bOut.getCount());
254                             _bOut=null;
255                         }
256 
257                         String etag=_wrapper.getETag();
258                         if (etag!=null)
259                             setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"');
260                         return;
261                     }
262                 }
263             }
264             
265             doNotCompress(true); // Send vary as it could have been compressed if encoding was present
266         }
267     }
268 
269     /**
270      * Do not compress.
271      *
272      * @throws IOException
273      *             Signals that an I/O exception has occurred.
274      */
275     public void doNotCompress(boolean sendVary) throws IOException
276     {
277         if (_compressedOutputStream != null)
278             throw new IllegalStateException("Compressed output stream is already assigned.");
279         if (_out == null || _bOut != null)
280         {
281             if (sendVary)
282                 addHeader("Vary",_vary);
283             if (_wrapper.getETag()!=null)
284                 setHeader("ETag",_wrapper.getETag());
285                 
286             _doNotCompress = true;
287 
288             _out = _response.getOutputStream();
289             setContentLength();
290 
291             if (_bOut != null)
292                 _out.write(_bOut.getBuf(),0,_bOut.getCount());
293             _bOut = null;
294         }
295     }
296 
297     /**
298      * Check out.
299      *
300      * @param lengthToWrite
301      *            the length
302      * @throws IOException
303      *             Signals that an I/O exception has occurred.
304      */
305     private void checkOut(int lengthToWrite) throws IOException
306     {
307         if (_closed)
308             throw new IOException("CLOSED");
309 
310         if (_out == null)
311         {            
312             // If this first write is larger than buffer size, then we are committing now
313             if (lengthToWrite>_wrapper.getBufferSize())
314             {
315                 // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
316                 long length=_wrapper.getContentLength();
317                 if (length>=0 && length<_wrapper.getMinCompressSize())
318                     doNotCompress(false);  // Not compressing by size, so no vary on request headers
319                 else
320                     doCompress();
321             }
322             else
323             {
324                 // start aggregating writes into a buffered output stream
325                 _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
326             }
327         }
328         // else are we aggregating writes?
329         else if (_bOut !=null)
330         {
331             // We are aggregating into the buffered output stream.  
332 
333             // If this write fills the buffer, then we are committing
334             if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount()))
335             {
336                 // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
337                 long length=_wrapper.getContentLength();
338                 if (length>=0 && length<_wrapper.getMinCompressSize())
339                     doNotCompress(false);  // Not compressing by size, so no vary on request headers
340                 else
341                     doCompress();
342             }
343         }
344     }
345 
346     public OutputStream getOutputStream()
347     {
348         return _out;
349     }
350 
351     public boolean isClosed()
352     {
353         return _closed;
354     }
355 
356     /**
357      * Allows derived implementations to replace PrintWriter implementation.
358      */
359     protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
360     {
361         return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
362     }
363 
364     protected void addHeader(String name,String value)
365     {
366         _response.addHeader(name, value);
367     }
368 
369     protected void setHeader(String name,String value)
370     {
371         _response.setHeader(name, value);
372     }
373 
374     @Override
375     public void setWriteListener(WriteListener writeListener)
376     {
377         // TODO 3.1 Auto-generated method stub
378         
379     }
380     
381 
382     @Override
383     public boolean isReady()
384     {
385         // TODO 3.1 Auto-generated method stub
386         return false;
387     }
388 
389     /**
390      * Create the stream fitting to the underlying compression type.
391      *
392      * @throws IOException
393      *             Signals that an I/O exception has occurred.
394      */
395     protected abstract DeflaterOutputStream createStream() throws IOException;
396 
397 
398 }