• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java $
3  * $Revision: 652091 $
4  * $Date: 2008-04-29 13:41:07 -0700 (Tue, 29 Apr 2008) $
5  *
6  * ====================================================================
7  * Licensed to the Apache Software Foundation (ASF) under one
8  * or more contributor license agreements.  See the NOTICE file
9  * distributed with this work for additional information
10  * regarding copyright ownership.  The ASF licenses this file
11  * to you under the Apache License, Version 2.0 (the
12  * "License"); you may not use this file except in compliance
13  * with the License.  You may obtain a copy of the License at
14  *
15  *   http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing,
18  * software distributed under the License is distributed on an
19  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20  * KIND, either express or implied.  See the License for the
21  * specific language governing permissions and limitations
22  * under the License.
23  * ====================================================================
24  *
25  * This software consists of voluntary contributions made by many
26  * individuals on behalf of the Apache Software Foundation.  For more
27  * information on the Apache Software Foundation, please see
28  * <http://www.apache.org/>.
29  *
30  */
31 
32 package org.apache.http.impl.io;
33 
34 import java.io.IOException;
35 import java.io.InputStream;
36 
37 import org.apache.http.io.SessionInputBuffer;
38 
39 /**
40  * Stream that cuts off after a specified number of bytes.
41  * Note that this class NEVER closes the underlying stream, even when close
42  * gets called.  Instead, it will read until the "end" of its chunking on
43  * close, which allows for the seamless execution of subsequent HTTP 1.1
44  * requests, while not requiring the client to remember to read the entire
45  * contents of the response.
46  *
47  * <p>Implementation note: Choices abound. One approach would pass
48  * through the {@link InputStream#mark} and {@link InputStream#reset} calls to
49  * the underlying stream.  That's tricky, though, because you then have to
50  * start duplicating the work of keeping track of how much a reset rewinds.
51  * Further, you have to watch out for the "readLimit", and since the semantics
52  * for the readLimit leave room for differing implementations, you might get
53  * into a lot of trouble.</p>
54  *
55  * <p>Alternatively, you could make this class extend
56  * {@link java.io.BufferedInputStream}
57  * and then use the protected members of that class to avoid duplicated effort.
58  * That solution has the side effect of adding yet another possible layer of
59  * buffering.</p>
60  *
61  * <p>Then, there is the simple choice, which this takes - simply don't
62  * support {@link InputStream#mark} and {@link InputStream#reset}.  That choice
63  * has the added benefit of keeping this class very simple.</p>
64  *
65  * @author Ortwin Glueck
66  * @author Eric Johnson
67  * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
68  *
69  * @since 4.0
70  */
71 public class ContentLengthInputStream extends InputStream {
72 
73     private static final int BUFFER_SIZE = 2048;
74     /**
75      * The maximum number of bytes that can be read from the stream. Subsequent
76      * read operations will return -1.
77      */
78     private long contentLength;
79 
80     /** The current position */
81     private long pos = 0;
82 
83     /** True if the stream is closed. */
84     private boolean closed = false;
85 
86     /**
87      * Wrapped input stream that all calls are delegated to.
88      */
89     private SessionInputBuffer in = null;
90 
91     /**
92      * Creates a new length limited stream
93      *
94      * @param in The session input buffer to wrap
95      * @param contentLength The maximum number of bytes that can be read from
96      * the stream. Subsequent read operations will return -1.
97      */
ContentLengthInputStream(final SessionInputBuffer in, long contentLength)98     public ContentLengthInputStream(final SessionInputBuffer in, long contentLength) {
99         super();
100         if (in == null) {
101             throw new IllegalArgumentException("Input stream may not be null");
102         }
103         if (contentLength < 0) {
104             throw new IllegalArgumentException("Content length may not be negative");
105         }
106         this.in = in;
107         this.contentLength = contentLength;
108     }
109 
110     /**
111      * <p>Reads until the end of the known length of content.</p>
112      *
113      * <p>Does not close the underlying socket input, but instead leaves it
114      * primed to parse the next response.</p>
115      * @throws IOException If an IO problem occurs.
116      */
close()117     public void close() throws IOException {
118         if (!closed) {
119             try {
120                 byte buffer[] = new byte[BUFFER_SIZE];
121                 while (read(buffer) >= 0) {
122                 }
123             } finally {
124                 // close after above so that we don't throw an exception trying
125                 // to read after closed!
126                 closed = true;
127             }
128         }
129     }
130 
131 
132     /**
133      * Read the next byte from the stream
134      * @return The next byte or -1 if the end of stream has been reached.
135      * @throws IOException If an IO problem occurs
136      * @see java.io.InputStream#read()
137      */
read()138     public int read() throws IOException {
139         if (closed) {
140             throw new IOException("Attempted read from closed stream.");
141         }
142 
143         if (pos >= contentLength) {
144             return -1;
145         }
146         pos++;
147         return this.in.read();
148     }
149 
150     /**
151      * Does standard {@link InputStream#read(byte[], int, int)} behavior, but
152      * also notifies the watcher when the contents have been consumed.
153      *
154      * @param b     The byte array to fill.
155      * @param off   Start filling at this position.
156      * @param len   The number of bytes to attempt to read.
157      * @return The number of bytes read, or -1 if the end of content has been
158      *  reached.
159      *
160      * @throws java.io.IOException Should an error occur on the wrapped stream.
161      */
read(byte[] b, int off, int len)162     public int read (byte[] b, int off, int len) throws java.io.IOException {
163         if (closed) {
164             throw new IOException("Attempted read from closed stream.");
165         }
166 
167         if (pos >= contentLength) {
168             return -1;
169         }
170 
171         if (pos + len > contentLength) {
172             len = (int) (contentLength - pos);
173         }
174         int count = this.in.read(b, off, len);
175         pos += count;
176         return count;
177     }
178 
179 
180     /**
181      * Read more bytes from the stream.
182      * @param b The byte array to put the new data in.
183      * @return The number of bytes read into the buffer.
184      * @throws IOException If an IO problem occurs
185      * @see java.io.InputStream#read(byte[])
186      */
read(byte[] b)187     public int read(byte[] b) throws IOException {
188         return read(b, 0, b.length);
189     }
190 
191     /**
192      * Skips and discards a number of bytes from the input stream.
193      * @param n The number of bytes to skip.
194      * @return The actual number of bytes skipped. <= 0 if no bytes
195      * are skipped.
196      * @throws IOException If an error occurs while skipping bytes.
197      * @see InputStream#skip(long)
198      */
skip(long n)199     public long skip(long n) throws IOException {
200         if (n <= 0) {
201             return 0;
202         }
203         byte[] buffer = new byte[BUFFER_SIZE];
204         // make sure we don't skip more bytes than are
205         // still available
206         long remaining = Math.min(n, this.contentLength - this.pos);
207         // skip and keep track of the bytes actually skipped
208         long count = 0;
209         while (remaining > 0) {
210             int l = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining));
211             if (l == -1) {
212                 break;
213             }
214             count += l;
215             remaining -= l;
216         }
217         this.pos += count;
218         return count;
219     }
220 }
221