|
1 /* |
|
2 * ==================================================================== |
|
3 * Licensed to the Apache Software Foundation (ASF) under one |
|
4 * or more contributor license agreements. See the NOTICE file |
|
5 * distributed with this work for additional information |
|
6 * regarding copyright ownership. The ASF licenses this file |
|
7 * to you under the Apache License, Version 2.0 (the |
|
8 * "License"); you may not use this file except in compliance |
|
9 * with the License. You may obtain a copy of the License at |
|
10 * |
|
11 * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 * |
|
13 * Unless required by applicable law or agreed to in writing, |
|
14 * software distributed under the License is distributed on an |
|
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
16 * KIND, either express or implied. See the License for the |
|
17 * specific language governing permissions and limitations |
|
18 * under the License. |
|
19 * ==================================================================== |
|
20 * |
|
21 * This software consists of voluntary contributions made by many |
|
22 * individuals on behalf of the Apache Software Foundation. For more |
|
23 * information on the Apache Software Foundation, please see |
|
24 * <http://www.apache.org/>. |
|
25 * |
|
26 */ |
|
27 |
|
28 package ch.boye.httpclientandroidlib.impl.io; |
|
29 |
|
30 import java.io.IOException; |
|
31 import java.io.InputStream; |
|
32 |
|
33 import ch.boye.httpclientandroidlib.ConnectionClosedException; |
|
34 import ch.boye.httpclientandroidlib.io.BufferInfo; |
|
35 import ch.boye.httpclientandroidlib.io.SessionInputBuffer; |
|
36 |
|
37 /** |
|
38 * Input stream that cuts off after a defined number of bytes. This class |
|
39 * is used to receive content of HTTP messages where the end of the content |
|
40 * entity is determined by the value of the <code>Content-Length header</code>. |
|
41 * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} |
|
42 * long. |
|
43 * <p> |
|
44 * Note that this class NEVER closes the underlying stream, even when close |
|
45 * gets called. Instead, it will read until the "end" of its limit on |
|
46 * close, which allows for the seamless execution of subsequent HTTP 1.1 |
|
47 * requests, while not requiring the client to remember to read the entire |
|
48 * contents of the response. |
|
49 * |
|
50 * |
|
51 * @since 4.0 |
|
52 */ |
|
53 public class ContentLengthInputStream extends InputStream { |
|
54 |
|
55 private static final int BUFFER_SIZE = 2048; |
|
56 /** |
|
57 * The maximum number of bytes that can be read from the stream. Subsequent |
|
58 * read operations will return -1. |
|
59 */ |
|
60 private long contentLength; |
|
61 |
|
62 /** The current position */ |
|
63 private long pos = 0; |
|
64 |
|
65 /** True if the stream is closed. */ |
|
66 private boolean closed = false; |
|
67 |
|
68 /** |
|
69 * Wrapped input stream that all calls are delegated to. |
|
70 */ |
|
71 private SessionInputBuffer in = null; |
|
72 |
|
73 /** |
|
74 * Wraps a session input buffer and cuts off output after a defined number |
|
75 * of bytes. |
|
76 * |
|
77 * @param in The session input buffer |
|
78 * @param contentLength The maximum number of bytes that can be read from |
|
79 * the stream. Subsequent read operations will return -1. |
|
80 */ |
|
81 public ContentLengthInputStream(final SessionInputBuffer in, long contentLength) { |
|
82 super(); |
|
83 if (in == null) { |
|
84 throw new IllegalArgumentException("Input stream may not be null"); |
|
85 } |
|
86 if (contentLength < 0) { |
|
87 throw new IllegalArgumentException("Content length may not be negative"); |
|
88 } |
|
89 this.in = in; |
|
90 this.contentLength = contentLength; |
|
91 } |
|
92 |
|
93 /** |
|
94 * <p>Reads until the end of the known length of content.</p> |
|
95 * |
|
96 * <p>Does not close the underlying socket input, but instead leaves it |
|
97 * primed to parse the next response.</p> |
|
98 * @throws IOException If an IO problem occurs. |
|
99 */ |
|
100 public void close() throws IOException { |
|
101 if (!closed) { |
|
102 try { |
|
103 if (pos < contentLength) { |
|
104 byte buffer[] = new byte[BUFFER_SIZE]; |
|
105 while (read(buffer) >= 0) { |
|
106 } |
|
107 } |
|
108 } finally { |
|
109 // close after above so that we don't throw an exception trying |
|
110 // to read after closed! |
|
111 closed = true; |
|
112 } |
|
113 } |
|
114 } |
|
115 |
|
116 public int available() throws IOException { |
|
117 if (this.in instanceof BufferInfo) { |
|
118 int len = ((BufferInfo) this.in).length(); |
|
119 return Math.min(len, (int) (this.contentLength - this.pos)); |
|
120 } else { |
|
121 return 0; |
|
122 } |
|
123 } |
|
124 |
|
125 /** |
|
126 * Read the next byte from the stream |
|
127 * @return The next byte or -1 if the end of stream has been reached. |
|
128 * @throws IOException If an IO problem occurs |
|
129 * @see java.io.InputStream#read() |
|
130 */ |
|
131 public int read() throws IOException { |
|
132 if (closed) { |
|
133 throw new IOException("Attempted read from closed stream."); |
|
134 } |
|
135 |
|
136 if (pos >= contentLength) { |
|
137 return -1; |
|
138 } |
|
139 int b = this.in.read(); |
|
140 if (b == -1) { |
|
141 if (pos < contentLength) { |
|
142 throw new ConnectionClosedException( |
|
143 "Premature end of Content-Length delimited message body (expected: " |
|
144 + contentLength + "; received: " + pos); |
|
145 } |
|
146 } else { |
|
147 pos++; |
|
148 } |
|
149 return b; |
|
150 } |
|
151 |
|
152 /** |
|
153 * Does standard {@link InputStream#read(byte[], int, int)} behavior, but |
|
154 * also notifies the watcher when the contents have been consumed. |
|
155 * |
|
156 * @param b The byte array to fill. |
|
157 * @param off Start filling at this position. |
|
158 * @param len The number of bytes to attempt to read. |
|
159 * @return The number of bytes read, or -1 if the end of content has been |
|
160 * reached. |
|
161 * |
|
162 * @throws java.io.IOException Should an error occur on the wrapped stream. |
|
163 */ |
|
164 public int read (byte[] b, int off, int len) throws java.io.IOException { |
|
165 if (closed) { |
|
166 throw new IOException("Attempted read from closed stream."); |
|
167 } |
|
168 |
|
169 if (pos >= contentLength) { |
|
170 return -1; |
|
171 } |
|
172 |
|
173 if (pos + len > contentLength) { |
|
174 len = (int) (contentLength - pos); |
|
175 } |
|
176 int count = this.in.read(b, off, len); |
|
177 if (count == -1 && pos < contentLength) { |
|
178 throw new ConnectionClosedException( |
|
179 "Premature end of Content-Length delimited message body (expected: " |
|
180 + contentLength + "; received: " + pos); |
|
181 } |
|
182 if (count > 0) { |
|
183 pos += count; |
|
184 } |
|
185 return count; |
|
186 } |
|
187 |
|
188 |
|
189 /** |
|
190 * Read more bytes from the stream. |
|
191 * @param b The byte array to put the new data in. |
|
192 * @return The number of bytes read into the buffer. |
|
193 * @throws IOException If an IO problem occurs |
|
194 * @see java.io.InputStream#read(byte[]) |
|
195 */ |
|
196 public int read(byte[] b) throws IOException { |
|
197 return read(b, 0, b.length); |
|
198 } |
|
199 |
|
200 /** |
|
201 * Skips and discards a number of bytes from the input stream. |
|
202 * @param n The number of bytes to skip. |
|
203 * @return The actual number of bytes skipped. <= 0 if no bytes |
|
204 * are skipped. |
|
205 * @throws IOException If an error occurs while skipping bytes. |
|
206 * @see InputStream#skip(long) |
|
207 */ |
|
208 public long skip(long n) throws IOException { |
|
209 if (n <= 0) { |
|
210 return 0; |
|
211 } |
|
212 byte[] buffer = new byte[BUFFER_SIZE]; |
|
213 // make sure we don't skip more bytes than are |
|
214 // still available |
|
215 long remaining = Math.min(n, this.contentLength - this.pos); |
|
216 // skip and keep track of the bytes actually skipped |
|
217 long count = 0; |
|
218 while (remaining > 0) { |
|
219 int l = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining)); |
|
220 if (l == -1) { |
|
221 break; |
|
222 } |
|
223 count += l; |
|
224 remaining -= l; |
|
225 } |
|
226 return count; |
|
227 } |
|
228 } |