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  * @deprecated Please use {@link java.net.URL#openConnection} instead.
72  *     Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
73  *     for further details.
74  */
75 @Deprecated
76 public class ContentLengthInputStream extends InputStream {
77 
78     private static final int BUFFER_SIZE = 2048;
79     /**
80      * The maximum number of bytes that can be read from the stream. Subsequent
81      * read operations will return -1.
82      */
83     private long contentLength;
84 
85     /** The current position */
86     private long pos = 0;
87 
88     /** True if the stream is closed. */
89     private boolean closed = false;
90 
91     /**
92      * Wrapped input stream that all calls are delegated to.
93      */
94     private SessionInputBuffer in = null;
95 
96     /**
97      * Creates a new length limited stream
98      *
99      * @param in The session input buffer to wrap
100      * @param contentLength The maximum number of bytes that can be read from
101      * the stream. Subsequent read operations will return -1.
102      */
ContentLengthInputStream(final SessionInputBuffer in, long contentLength)103     public ContentLengthInputStream(final SessionInputBuffer in, long contentLength) {
104         super();
105         if (in == null) {
106             throw new IllegalArgumentException("Input stream may not be null");
107         }
108         if (contentLength < 0) {
109             throw new IllegalArgumentException("Content length may not be negative");
110         }
111         this.in = in;
112         this.contentLength = contentLength;
113     }
114 
115     /**
116      * <p>Reads until the end of the known length of content.</p>
117      *
118      * <p>Does not close the underlying socket input, but instead leaves it
119      * primed to parse the next response.</p>
120      * @throws IOException If an IO problem occurs.
121      */
close()122     public void close() throws IOException {
123         if (!closed) {
124             try {
125                 byte buffer[] = new byte[BUFFER_SIZE];
126                 while (read(buffer) >= 0) {
127                 }
128             } finally {
129                 // close after above so that we don't throw an exception trying
130                 // to read after closed!
131                 closed = true;
132             }
133         }
134     }
135 
136 
137     /**
138      * Read the next byte from the stream
139      * @return The next byte or -1 if the end of stream has been reached.
140      * @throws IOException If an IO problem occurs
141      * @see java.io.InputStream#read()
142      */
read()143     public int read() throws IOException {
144         if (closed) {
145             throw new IOException("Attempted read from closed stream.");
146         }
147 
148         if (pos >= contentLength) {
149             return -1;
150         }
151         pos++;
152         return this.in.read();
153     }
154 
155     /**
156      * Does standard {@link InputStream#read(byte[], int, int)} behavior, but
157      * also notifies the watcher when the contents have been consumed.
158      *
159      * @param b     The byte array to fill.
160      * @param off   Start filling at this position.
161      * @param len   The number of bytes to attempt to read.
162      * @return The number of bytes read, or -1 if the end of content has been
163      *  reached.
164      *
165      * @throws java.io.IOException Should an error occur on the wrapped stream.
166      */
read(byte[] b, int off, int len)167     public int read (byte[] b, int off, int len) throws java.io.IOException {
168         if (closed) {
169             throw new IOException("Attempted read from closed stream.");
170         }
171 
172         if (pos >= contentLength) {
173             return -1;
174         }
175 
176         if (pos + len > contentLength) {
177             len = (int) (contentLength - pos);
178         }
179         int count = this.in.read(b, off, len);
180         pos += count;
181         return count;
182     }
183 
184 
185     /**
186      * Read more bytes from the stream.
187      * @param b The byte array to put the new data in.
188      * @return The number of bytes read into the buffer.
189      * @throws IOException If an IO problem occurs
190      * @see java.io.InputStream#read(byte[])
191      */
read(byte[] b)192     public int read(byte[] b) throws IOException {
193         return read(b, 0, b.length);
194     }
195 
196     /**
197      * Skips and discards a number of bytes from the input stream.
198      * @param n The number of bytes to skip.
199      * @return The actual number of bytes skipped. <= 0 if no bytes
200      * are skipped.
201      * @throws IOException If an error occurs while skipping bytes.
202      * @see InputStream#skip(long)
203      */
skip(long n)204     public long skip(long n) throws IOException {
205         if (n <= 0) {
206             return 0;
207         }
208         byte[] buffer = new byte[BUFFER_SIZE];
209         // make sure we don't skip more bytes than are
210         // still available
211         long remaining = Math.min(n, this.contentLength - this.pos);
212         // skip and keep track of the bytes actually skipped
213         long count = 0;
214         while (remaining > 0) {
215             int l = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining));
216             if (l == -1) {
217                 break;
218             }
219             count += l;
220             remaining -= l;
221         }
222         this.pos += count;
223         return count;
224     }
225 }
226