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      /**
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                             setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"');
259                         return;
260                     }
261                 }
262             }
263             
264             doNotCompress(true); // Send vary as it could have been compressed if encoding was present
265         }
266     }
267 
268     /**
269      * Do not compress.
270      *
271      * @throws IOException
272      *             Signals that an I/O exception has occurred.
273      */
274     public void doNotCompress(boolean sendVary) throws IOException
275     {
276         if (_compressedOutputStream != null)
277             throw new IllegalStateException("Compressed output stream is already assigned.");
278         if (_out == null || _bOut != null)
279         {
280             if (sendVary)
281                 addHeader("Vary",_vary);
282             if (_wrapper.getETag()!=null)
283                 setHeader("ETag",_wrapper.getETag());
284                 
285             _doNotCompress = true;
286 
287             _out = _response.getOutputStream();
288             setContentLength();
289 
290             if (_bOut != null)
291                 _out.write(_bOut.getBuf(),0,_bOut.getCount());
292             _bOut = null;
293         }
294     }
295 
296     /**
297      * Check out.
298      *
299      * @param lengthToWrite
300      *            the length
301      * @throws IOException
302      *             Signals that an I/O exception has occurred.
303      */
304     private void checkOut(int lengthToWrite) throws IOException
305     {
306         if (_closed)
307             throw new IOException("CLOSED");
308 
309         if (_out == null)
310         {            
311             // If this first write is larger than buffer size, then we are committing now
312             if (lengthToWrite>_wrapper.getBufferSize())
313             {
314                 // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
315                 long length=_wrapper.getContentLength();
316                 if (length>=0 && length<_wrapper.getMinCompressSize())
317                     doNotCompress(false);  // Not compressing by size, so no vary on request headers
318                 else
319                     doCompress();
320             }
321             else
322             {
323                 // start aggregating writes into a buffered output stream
324                 _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
325             }
326         }
327         // else are we aggregating writes?
328         else if (_bOut !=null)
329         {
330             // We are aggregating into the buffered output stream.  
331 
332             // If this write fills the buffer, then we are committing
333             if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount()))
334             {
335                 // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
336                 long length=_wrapper.getContentLength();
337                 if (length>=0 && length<_wrapper.getMinCompressSize())
338                     doNotCompress(false);  // Not compressing by size, so no vary on request headers
339                 else
340                     doCompress();
341             }
342         }
343     }
344 
345     /**
346      * @see org.eclipse.jetty.servlets.gzip.CompressedStream#getOutputStream()
347      */
348     public OutputStream getOutputStream()
349     {
350         return _out;
351     }
352 
353     /**
354      * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
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     /**
380      * Create the stream fitting to the underlying compression type.
381      *
382      * @throws IOException
383      *             Signals that an I/O exception has occurred.
384      */
385     protected abstract DeflaterOutputStream createStream() throws IOException;
386 
387 
388 }