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