View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Attic/ResponseInputStream.java,v 1.23.2.1 2004/02/22 18:21:13 olegk Exp $
3    * $Revision: 1.23.2.1 $
4    * $Date: 2004/02/22 18:21:13 $
5    *
6    * ====================================================================
7    *
8    *  Copyright 1999-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   * [Additional notices, if required by prior licensing conditions]
29   *
30   */
31  
32  package org.apache.commons.httpclient;
33  
34  import java.io.IOException;
35  import java.io.InputStream;
36  
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.commons.logging.Log;
39  
40  /***
41   * <p>{@link InputStream} wrapper supporting the chunked transfer encoding.</p>
42   *
43   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
44   * @author Sean C. Sullivan
45   * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
46   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
47   * @version $Revision: 1.23.2.1 $ $Date: 2004/02/22 18:21:13 $
48   *
49   * @deprecated Use new ChunkedInputStream(HttpConnecion#getResponseInputStream());
50   *
51   */
52  public class ResponseInputStream extends InputStream {
53  
54      // -------------------------------------------------------- Class Variables
55      
56      /*** Log object for this class. */
57      public static final Log LOG = LogFactory.getLog(ResponseInputStream.class);
58  
59      // ----------------------------------------------------------- Constructors
60  
61      /***
62       *
63       * @param stream Must be non-null.
64       * @param chunked <code>true</code> if the input stream is chunked
65       * @param contentLength content length
66       *
67       * @deprecated Use ChunkedInputStream;
68       */
69      public ResponseInputStream(InputStream stream, boolean chunked, int contentLength) {
70          LOG.trace("enter ResponseInputStream(InputStream, boolean, int)");
71  
72          if (null == stream) {
73              throw new NullPointerException("InputStream parameter is null");
74          }
75          closed = false;
76          count = 0;
77          this.chunk = chunked;
78          this.contentLength = contentLength;
79          this.stream = stream;
80      }
81  
82      /***
83       * Construct a servlet input stream associated with the specified Request.
84       *
85       * @param stream Must be non-null.
86       * @param method Must be non-null.
87       *
88       * @deprecated Use ChunkedInputStream;
89       *
90       */
91      public ResponseInputStream(InputStream stream, HttpMethod method) {
92          super();
93          LOG.trace("enter ResponseInputStream(InputStream, HttpMethod)");
94  
95          if (null == stream) {
96              throw new NullPointerException("InputStream parameter is null");
97          }
98  
99          if (null == method) {
100             throw new NullPointerException("HttpMethod parameter is null");
101         }
102 
103         closed = false;
104         count = 0;
105 
106         // Retrieving transfer encoding header
107         Header transferEncoding = method.getResponseHeader("transfer-encoding");
108         if ((null != transferEncoding) && (transferEncoding.getValue().
109                              toLowerCase().indexOf("chunked") != -1)) {
110             chunk = true;
111         }
112 
113         // Retrieving content length header
114         Header contentLengthHeader = method.getResponseHeader("content-length");
115         if (null != contentLengthHeader) {
116             try {
117                 this.contentLength =
118                     Integer.parseInt(contentLengthHeader.getValue());
119             } catch (NumberFormatException ignored) {
120                    // we are intentionally ignoring the NumberFormatException
121             }
122         }
123 
124         this.stream = stream;
125     }
126 
127 
128     // ----------------------------------------------------- Instance Variables
129 
130     /***
131      * Has this stream been closed?
132      */
133     private boolean closed = false;
134 
135     /***
136      * Use chunking ?
137      */
138     private boolean chunk = false;
139 
140     /***
141      * True if the final chunk was found.
142      */
143     private boolean endChunk = false;
144 
145     /***
146      * Chunk buffer.
147      */
148     private byte[] buffer = null;
149 
150     /***
151      * Current chunk length.
152      */
153     private int length = 0;
154 
155     /***
156      * Current chunk buffer position.
157      */
158     private int pos = 0;
159 
160     /***
161      * The number of bytes which have already been returned by this stream.
162      */
163     private int count = 0;
164 
165     /***
166      * The content length past which we will not read, or -1 if there is
167      * no defined content length.
168      */
169     private int contentLength = -1;
170 
171     /***
172      * The underlying input stream from which we should read data.
173      */
174     private InputStream stream = null;
175 
176     // --------------------------------------------------------- Public Methods
177 
178     /***
179      * Close this input stream.  No physical level I-O is performed, but
180      * any further attempt to read from this stream will throw an IOException.
181      * If a content length has been set but not all of the bytes have yet been
182      * consumed, the remaining bytes will be swallowed.
183      * 
184      * @throws IOException If an IO problem occurs.
185      *
186      * @deprecated Use ChunkedInputStream;
187      */
188     public void close() throws IOException {
189         LOG.trace("enter ResponseInputStream.close()");
190     /*
191         // Xerces appears to doubly-close the input stream...
192         if(closed) {
193             throw new IOException("Stream is already closed");
194         }
195     */
196 
197         //TODO: This close code is not very robust
198         if (!closed) {
199             try {
200                 if (chunk) {
201                     while (!endChunk) {
202                         int b = read();
203                         if (b < 0) {
204                             break;
205                         }
206                     }
207                 } else {
208                     if (length > 0) {
209                         while (count < length) {
210                             int b = read();
211                             if (b < 0) {
212                                 break;
213                             }
214                         }
215                     }
216                 }
217             } catch (java.io.IOException ex) {
218                 throw ex;
219             } finally {
220                     closed = true;
221             }
222         }
223     }
224 
225 
226     /***
227      * Read up to <code>len</code> bytes of data from the input stream
228      * into an array of bytes.  An attempt is made to read as many as
229      * <code>len</code> bytes, but a smaller number may be read,
230      * possibly zero.  The number of bytes actually read is returned as
231      * an integer.  This method blocks until input data is available,
232      * end of file is detected, or an exception is thrown.
233      *
234      * @param b The buffer into which the data is read
235      * @param off The start offset into array <code>b</code> at which
236      *  the data is written
237      * @param len The maximum number of bytes to read
238      * @return The number of bytes that were read.
239      *
240      * @exception IOException if an input/output error occurs
241      *
242      * @deprecated Use ChunkedInputStream;
243      */
244     public int read(byte b[], int off, int len)
245     throws IOException {
246         LOG.trace("enter ResponseInputStream.read(byte, int, int)");
247 
248         int avail = length - pos;
249         if ((avail == 0) && (!fillBuffer())) {
250             return (-1);
251         }
252 
253         avail = length - pos;
254         if (avail == 0) {
255             return (-1);
256         }
257 
258         int toCopy = avail;
259 
260         if (toCopy < 0) {
261           return (-1);
262         }
263 
264         if (avail > len) {
265             toCopy = len;
266         }
267         System.arraycopy(buffer, pos, b, off, toCopy);
268         pos += toCopy;
269         return toCopy;
270     }
271 
272     /***
273      * Read and return a single byte from this input stream, or -1 if end of
274      * file has been encountered.
275      *
276      * @return The next byte in the stream or -1.
277      * @exception IOException if an input/output error occurs
278      *
279      * @deprecated Use ChunkedInputStream;
280      */
281     public int read() throws IOException {
282         LOG.trace("enter ResponseInputStream.read()");
283 
284         if (pos == length) {
285             if (!fillBuffer()) {
286                 return (-1);
287             }
288         }
289 
290         return (buffer[pos++] & 0xff);
291 
292     }
293 
294     // -------------------------------------------------------- Private Methods
295 
296 
297     /***
298      * Fill the chunk buffer.
299      * @return true If successful
300      * @throws IOException If an IO problem occurs.
301      *
302      * @deprecated Use ChunkedInputStream;
303      */
304     private boolean fillBuffer() throws IOException {
305         LOG.trace("enter ResponseInputStream.fillBuffer()");
306 
307         // Has this stream been closed?
308         if (closed) {
309             return false;
310         }
311             //throw new IOException("Stream is closed");
312 
313         if (endChunk) {
314             return false;
315         }
316 
317         // Have we read the specified content length already?
318         if ((contentLength >= 0) && (count >= contentLength)) {
319             return false;    // End of file indicator
320         }
321 
322         pos = 0;
323 
324         if (chunk) {
325 
326             try {
327                 String numberValue = readLineFromStream();
328                 if (numberValue == null) {
329                   throw new NumberFormatException("unable to find chunk length");
330                 }
331 
332                 length = Integer.parseInt(numberValue.trim(), 16);
333             } catch (NumberFormatException e) {
334                 // Critical error, unable to parse the chunk length
335                 length = -1;
336                 chunk = false;
337                 endChunk = true;
338                 closed = true;
339                 return false;
340             }
341 
342             if (length == 0) {
343 
344                 // Skipping trailing headers, if any
345                 String trailingLine = readLineFromStream();
346                 while (!trailingLine.equals("")) {
347                     trailingLine = readLineFromStream();
348                 }
349                 endChunk = true;
350                 return false;
351 
352             } else {
353 
354                 if ((buffer == null) || (length > buffer.length)) {
355                     buffer = new byte[length];
356                 }
357 
358                 // Now read the whole chunk into the buffer
359 
360                 int nbRead = 0;
361                 int currentRead = 0;
362 
363                 while (nbRead < length) {
364                     try {
365                         currentRead = stream.read(buffer, nbRead,
366                                         length - nbRead);
367                     } catch (Throwable t) {
368                         LOG.debug("Exception thrown reading chunk from response", t);
369                         throw new IOException();
370                     }
371                     if (currentRead < 0) {
372                         throw new IOException("Not enough bytes read");
373                     }
374                     nbRead += currentRead;
375                 }
376 
377                 // Skipping the CRLF
378                 readLineFromStream();
379 
380             }
381 
382         } else { //not using chunking
383 
384             try {
385                 if (buffer == null) {
386                     buffer = new byte[4096];
387                 }
388                 length = stream.read(buffer);
389                 count += length;
390             } catch (Throwable t) {
391                 LOG.debug("Exception thrown reading from response", t);
392                 throw new IOException(t.getMessage());
393             }
394 
395         }
396 
397         return true;
398 
399     }
400 
401     /***
402      * Reads the input stream, one line at a time. Reads bytes into an array,
403      * until it reads a certain number of bytes or reaches a newline character,
404      * which it reads into the array as well.
405      *
406      * @return The line that was read, or <code>null</code> if end-of-file
407      *  was encountered
408      * @exception IOException   if an input or output exception has occurred
409      *
410      * @deprecated Use ChunkedInputStream;
411      */
412     private String readLineFromStream()
413     throws IOException {
414         LOG.trace("enter ResponseInputStream.ReadLineFromStream()");
415 
416         StringBuffer sb = new StringBuffer();
417         while (true) {
418             int ch = stream.read();
419             if (ch < 0) {
420                 if (sb.length() == 0) {
421                     return (null);
422                 } else {
423                     break;
424                 }
425             } else if (ch == '\r') {
426                 continue;
427             } else if (ch == '\n') {
428                 break;
429             }
430             sb.append((char) ch);
431         }
432         return (sb.toString());
433     }
434 }