View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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  
27  import javax.servlet.ServletOutputStream;
28  import javax.servlet.WriteListener;
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 OutputStream _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      /**
70       * Reset buffer.
71       */
72      public void resetBuffer()
73      {
74          if (_response.isCommitted() || _compressedOutputStream!=null )
75              throw new IllegalStateException("Committed");
76          _closed = false;
77          _out = null;
78          _bOut = null;
79          _doNotCompress = false;
80      }
81  
82      /* ------------------------------------------------------------ */
83      public void setBufferSize(int bufferSize)
84      {
85          if (_bOut!=null && _bOut.getBuf().length<bufferSize)
86          {
87              ByteArrayOutputStream2 b = new ByteArrayOutputStream2(bufferSize);
88              b.write(_bOut.getBuf(),0,_bOut.size());
89              _bOut=b;
90          }
91      }
92      
93      /* ------------------------------------------------------------ */
94      public void setContentLength()
95      {
96          if (_doNotCompress)
97          {
98              long length=_wrapper.getContentLength();
99              if (length>=0)
100             {
101                 if (length < Integer.MAX_VALUE)
102                     _response.setContentLength((int)length);
103                 else
104                     _response.setHeader("Content-Length",Long.toString(length));
105             }
106         }
107     }
108 
109     /* ------------------------------------------------------------ */
110     /**
111      * @see java.io.OutputStream#flush()
112      */
113     @Override
114     public void flush() throws IOException
115     {
116         if (_out == null || _bOut != null)
117         {
118             long length=_wrapper.getContentLength();
119             if (length > 0 && length < _wrapper.getMinCompressSize())
120                 doNotCompress(false);
121             else
122                 doCompress();
123         }
124 
125         _out.flush();
126     }
127 
128     /* ------------------------------------------------------------ */
129     /**
130      * @see java.io.OutputStream#close()
131      */
132     @Override
133     public void close() throws IOException
134     {
135         if (_closed)
136             return;
137 
138         if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
139             flush();
140         else
141         {
142             if (_bOut != null)
143             {
144                 long length=_wrapper.getContentLength();
145                 if (length < 0)
146                 {
147                     length = _bOut.getCount();
148                     _wrapper.setContentLength(length);
149                 }
150                 if (length < _wrapper.getMinCompressSize())
151                     doNotCompress(false);
152                 else
153                     doCompress();
154             }
155             else if (_out == null)
156             {
157                 // No output
158                 doNotCompress(false);
159             }
160 
161             if (_compressedOutputStream != null)
162                 _compressedOutputStream.close();
163             else
164                 _out.close();
165             _closed = true;
166         }
167     }
168 
169     /**
170      * Finish.
171      *
172      * @throws IOException
173      *             Signals that an I/O exception has occurred.
174      */
175     public void finish() throws IOException
176     {
177         if (!_closed)
178         {
179             if (_out == null || _bOut != null)
180             {
181                 long length=_wrapper.getContentLength();
182                 if (length<0 &&_bOut==null || length >= 0 && length < _wrapper.getMinCompressSize())
183                     doNotCompress(false);
184                 else
185                     doCompress();
186             }
187 
188             if (_compressedOutputStream != null && !_closed)
189             {
190                 _closed = true;
191                 _compressedOutputStream.close();
192             }
193         }
194     }
195 
196     /* ------------------------------------------------------------ */
197     /**
198      * @see java.io.OutputStream#write(int)
199      */
200     @Override
201     public void write(int b) throws IOException
202     {
203         checkOut(1);
204         _out.write(b);
205     }
206 
207     /* ------------------------------------------------------------ */
208     /**
209      * @see java.io.OutputStream#write(byte[])
210      */
211     @Override
212     public void write(byte b[]) throws IOException
213     {
214         checkOut(b.length);
215         _out.write(b);
216     }
217 
218     /* ------------------------------------------------------------ */
219     /**
220      * @see java.io.OutputStream#write(byte[], int, int)
221      */
222     @Override
223     public void write(byte b[], int off, int len) throws IOException
224     {
225         checkOut(len);
226         _out.write(b,off,len);
227     }
228 
229     /**
230      * Do compress.
231      *
232      * @throws IOException Signals that an I/O exception has occurred.
233      */
234     public void doCompress() throws IOException
235     {
236         if (_compressedOutputStream==null)
237         {
238             if (_response.isCommitted())
239                 throw new IllegalStateException();
240 
241             if (_encoding!=null)
242             {
243                 setHeader("Content-Encoding", _encoding);
244                 if (_response.containsHeader("Content-Encoding"))
245                 {
246                     addHeader("Vary",_vary);
247                     _out=_compressedOutputStream=createStream();
248                     if (_out!=null)
249                     {
250                         if (_bOut!=null)
251                         {
252                             _out.write(_bOut.getBuf(),0,_bOut.getCount());
253                             _bOut=null;
254                         }
255 
256                         String etag=_wrapper.getETag();
257                         if (etag!=null)
258                         {
259                             int end = etag.length()-1;
260                             if (etag.charAt(end)=='"')
261                                 setHeader("ETag",etag.substring(0,end)+"--"+_encoding+'"');
262                             else
263                                 setHeader("ETag",etag+"--"+_encoding);
264                         }
265                         return;
266                     }
267                 }
268             }
269             
270             doNotCompress(true); // Send vary as it could have been compressed if encoding was present
271         }
272     }
273 
274     /**
275      * Do not compress.
276      *
277      * @throws IOException
278      *             Signals that an I/O exception has occurred.
279      */
280     public void doNotCompress(boolean sendVary) throws IOException
281     {
282         if (_compressedOutputStream != null)
283             throw new IllegalStateException("Compressed output stream is already assigned.");
284         if (_out == null || _bOut != null)
285         {
286             if (sendVary)
287                 addHeader("Vary",_vary);
288             if (_wrapper.getETag()!=null)
289                 setHeader("ETag",_wrapper.getETag());
290                 
291             _doNotCompress = true;
292 
293             _out = _response.getOutputStream();
294             setContentLength();
295 
296             if (_bOut != null)
297                 _out.write(_bOut.getBuf(),0,_bOut.getCount());
298             _bOut = null;
299         }
300     }
301 
302     /**
303      * Check out.
304      *
305      * @param lengthToWrite
306      *            the length
307      * @throws IOException
308      *             Signals that an I/O exception has occurred.
309      */
310     private void checkOut(int lengthToWrite) throws IOException
311     {
312         if (_closed)
313             throw new IOException("CLOSED");
314 
315         if (_out == null)
316         {            
317             // If this first write is larger than buffer size, then we are committing now
318             if (lengthToWrite>_wrapper.getBufferSize())
319             {
320                 // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
321                 long length=_wrapper.getContentLength();
322                 if (length>=0 && length<_wrapper.getMinCompressSize())
323                     doNotCompress(false);  // Not compressing by size, so no vary on request headers
324                 else
325                     doCompress();
326             }
327             else
328             {
329                 // start aggregating writes into a buffered output stream
330                 _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
331             }
332         }
333         // else are we aggregating writes?
334         else if (_bOut !=null)
335         {
336             // We are aggregating into the buffered output stream.  
337 
338             // If this write fills the buffer, then we are committing
339             if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount()))
340             {
341                 // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
342                 long length=_wrapper.getContentLength();
343                 if (length>=0 && length<_wrapper.getMinCompressSize())
344                     doNotCompress(false);  // Not compressing by size, so no vary on request headers
345                 else
346                     doCompress();
347             }
348         }
349     }
350 
351     public OutputStream getOutputStream()
352     {
353         return _out;
354     }
355 
356     public boolean isClosed()
357     {
358         return _closed;
359     }
360 
361     /**
362      * Allows derived implementations to replace PrintWriter implementation.
363      */
364     protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
365     {
366         return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
367     }
368 
369     protected void addHeader(String name,String value)
370     {
371         _response.addHeader(name, value);
372     }
373 
374     protected void setHeader(String name,String value)
375     {
376         _response.setHeader(name, value);
377     }
378 
379     @Override
380     public void setWriteListener(WriteListener writeListener)
381     {
382         throw new UnsupportedOperationException("Use AsyncGzipFilter");
383     }
384     
385 
386     @Override
387     public boolean isReady()
388     {
389         throw new UnsupportedOperationException("Use AsyncGzipFilter");
390     }
391 
392     /**
393      * Create the stream fitting to the underlying compression type.
394      *
395      * @throws IOException
396      *             Signals that an I/O exception has occurred.
397      */
398     protected abstract OutputStream createStream() throws IOException;
399 
400 
401 }