001: /*
002: * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0-beta1/module-main/src/main/java/org/apache/http/impl/io/ContentLengthInputStream.java $
003: * $Revision: 560343 $
004: * $Date: 2007-07-27 20:18:19 +0200 (Fri, 27 Jul 2007) $
005: *
006: * ====================================================================
007: * Licensed to the Apache Software Foundation (ASF) under one
008: * or more contributor license agreements. See the NOTICE file
009: * distributed with this work for additional information
010: * regarding copyright ownership. The ASF licenses this file
011: * to you under the Apache License, Version 2.0 (the
012: * "License"); you may not use this file except in compliance
013: * with the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing,
018: * software distributed under the License is distributed on an
019: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020: * KIND, either express or implied. See the License for the
021: * specific language governing permissions and limitations
022: * under the License.
023: * ====================================================================
024: *
025: * This software consists of voluntary contributions made by many
026: * individuals on behalf of the Apache Software Foundation. For more
027: * information on the Apache Software Foundation, please see
028: * <http://www.apache.org/>.
029: *
030: */
031:
032: package org.apache.http.impl.io;
033:
034: import java.io.IOException;
035: import java.io.InputStream;
036:
037: import org.apache.http.io.SessionInputBuffer;
038:
039: /**
040: * Stream that cuts off after a specified number of bytes.
041: * Note that this class NEVER closes the underlying stream, even when close
042: * gets called. Instead, it will read until the "end" of its chunking on
043: * close, which allows for the seamless execution of subsequent HTTP 1.1
044: * requests, while not requiring the client to remember to read the entire
045: * contents of the response.
046: *
047: * <p>Implementation note: Choices abound. One approach would pass
048: * through the {@link InputStream#mark} and {@link InputStream#reset} calls to
049: * the underlying stream. That's tricky, though, because you then have to
050: * start duplicating the work of keeping track of how much a reset rewinds.
051: * Further, you have to watch out for the "readLimit", and since the semantics
052: * for the readLimit leave room for differing implementations, you might get
053: * into a lot of trouble.</p>
054: *
055: * <p>Alternatively, you could make this class extend
056: * {@link java.io.BufferedInputStream}
057: * and then use the protected members of that class to avoid duplicated effort.
058: * That solution has the side effect of adding yet another possible layer of
059: * buffering.</p>
060: *
061: * <p>Then, there is the simple choice, which this takes - simply don't
062: * support {@link InputStream#mark} and {@link InputStream#reset}. That choice
063: * has the added benefit of keeping this class very simple.</p>
064: *
065: * @author Ortwin Glueck
066: * @author Eric Johnson
067: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
068: *
069: * @since 4.0
070: */
071: public class ContentLengthInputStream extends InputStream {
072:
073: private static int BUFFER_SIZE = 2048;
074: /**
075: * The maximum number of bytes that can be read from the stream. Subsequent
076: * read operations will return -1.
077: */
078: private long contentLength;
079:
080: /** The current position */
081: private long pos = 0;
082:
083: /** True if the stream is closed. */
084: private boolean closed = false;
085:
086: /**
087: * Wrapped input stream that all calls are delegated to.
088: */
089: private SessionInputBuffer in = null;
090:
091: /**
092: * Creates a new length limited stream
093: *
094: * @param in The session input buffer to wrap
095: * @param contentLength The maximum number of bytes that can be read from
096: * the stream. Subsequent read operations will return -1.
097: */
098: public ContentLengthInputStream(final SessionInputBuffer in,
099: long contentLength) {
100: super ();
101: if (in == null) {
102: throw new IllegalArgumentException(
103: "Input stream may not be null");
104: }
105: if (contentLength < 0) {
106: throw new IllegalArgumentException(
107: "Content length may not be negative");
108: }
109: this .in = in;
110: this .contentLength = contentLength;
111: }
112:
113: /**
114: * <p>Reads until the end of the known length of content.</p>
115: *
116: * <p>Does not close the underlying socket input, but instead leaves it
117: * primed to parse the next response.</p>
118: * @throws IOException If an IO problem occurs.
119: */
120: public void close() throws IOException {
121: if (!closed) {
122: try {
123: byte buffer[] = new byte[BUFFER_SIZE];
124: while (read(buffer) >= 0) {
125: }
126: } finally {
127: // close after above so that we don't throw an exception trying
128: // to read after closed!
129: closed = true;
130: }
131: }
132: }
133:
134: /**
135: * Read the next byte from the stream
136: * @return The next byte or -1 if the end of stream has been reached.
137: * @throws IOException If an IO problem occurs
138: * @see java.io.InputStream#read()
139: */
140: public int read() throws IOException {
141: if (closed) {
142: throw new IOException("Attempted read from closed stream.");
143: }
144:
145: if (pos >= contentLength) {
146: return -1;
147: }
148: pos++;
149: return this .in.read();
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)
165: throws java.io.IOException {
166: if (closed) {
167: throw new IOException("Attempted read from closed stream.");
168: }
169:
170: if (pos >= contentLength) {
171: return -1;
172: }
173:
174: if (pos + len > contentLength) {
175: len = (int) (contentLength - pos);
176: }
177: int count = this .in.read(b, off, len);
178: pos += count;
179: return count;
180: }
181:
182: /**
183: * Read more bytes from the stream.
184: * @param b The byte array to put the new data in.
185: * @return The number of bytes read into the buffer.
186: * @throws IOException If an IO problem occurs
187: * @see java.io.InputStream#read(byte[])
188: */
189: public int read(byte[] b) throws IOException {
190: return read(b, 0, b.length);
191: }
192:
193: /**
194: * Skips and discards a number of bytes from the input stream.
195: * @param n The number of bytes to skip.
196: * @return The actual number of bytes skipped. <= 0 if no bytes
197: * are skipped.
198: * @throws IOException If an error occurs while skipping bytes.
199: * @see InputStream#skip(long)
200: */
201: public long skip(long n) throws IOException {
202: if (n <= 0) {
203: return 0;
204: }
205: byte[] buffer = new byte[BUFFER_SIZE];
206: // make sure we don't skip more bytes than are
207: // still available
208: long remaining = Math.min(n, this .contentLength - this .pos);
209: // skip and keep track of the bytes actually skipped
210: long count = 0;
211: while (remaining > 0) {
212: int l = read(buffer, 0, (int) Math.min(BUFFER_SIZE,
213: remaining));
214: if (l == -1) {
215: break;
216: }
217: count += l;
218: remaining -= l;
219: }
220: this.pos += count;
221: return count;
222: }
223: }
|