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 org.apache.http.impl.io; 29 30 import java.io.IOException; 31 import java.io.InputStream; 32 33 import org.apache.http.ConnectionClosedException; 34 import org.apache.http.io.BufferInfo; 35 import org.apache.http.io.SessionInputBuffer; 36 import org.apache.http.util.Args; 37 38 /** 39 * Input stream that cuts off after a defined number of bytes. This class 40 * is used to receive content of HTTP messages where the end of the content 41 * entity is determined by the value of the {@code Content-Length header}. 42 * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} 43 * long. 44 * <p> 45 * Note that this class NEVER closes the underlying stream, even when close 46 * gets called. Instead, it will read until the "end" of its limit on 47 * close, which allows for the seamless execution of subsequent HTTP 1.1 48 * requests, while not requiring the client to remember to read the entire 49 * contents of the response. 50 * 51 * 52 * @since 4.0 53 */ 54 public class ContentLengthInputStream extends InputStream { 55 56 private static final int BUFFER_SIZE = 2048; 57 /** 58 * The maximum number of bytes that can be read from the stream. Subsequent 59 * read operations will return -1. 60 */ 61 private final long contentLength; 62 63 /** The current position */ 64 private long pos = 0; 65 66 /** True if the stream is closed. */ 67 private boolean closed = false; 68 69 /** 70 * Wrapped input stream that all calls are delegated to. 71 */ 72 private SessionInputBuffer in = null; 73 74 /** 75 * Wraps a session input buffer and cuts off output after a defined number 76 * of bytes. 77 * 78 * @param in The session input buffer 79 * @param contentLength The maximum number of bytes that can be read from 80 * the stream. Subsequent read operations will return -1. 81 */ 82 public ContentLengthInputStream(final SessionInputBuffer in, final long contentLength) { 83 super(); 84 this.in = Args.notNull(in, "Session input buffer"); 85 this.contentLength = Args.notNegative(contentLength, "Content length"); 86 } 87 88 /** 89 * <p>Reads until the end of the known length of content.</p> 90 * 91 * <p>Does not close the underlying socket input, but instead leaves it 92 * primed to parse the next response.</p> 93 * @throws IOException If an IO problem occurs. 94 */ 95 @Override 96 public void close() throws IOException { 97 if (!closed) { 98 try { 99 if (pos < contentLength) { 100 final byte buffer[] = new byte[BUFFER_SIZE]; 101 while (read(buffer) >= 0) { 102 // do nothing. 103 } 104 } 105 } finally { 106 // close after above so that we don't throw an exception trying 107 // to read after closed! 108 closed = true; 109 } 110 } 111 } 112 113 @Override 114 public int available() throws IOException { 115 if (this.in instanceof BufferInfo) { 116 final int len = ((BufferInfo) this.in).length(); 117 return Math.min(len, (int) (this.contentLength - this.pos)); 118 } 119 return 0; 120 } 121 122 /** 123 * Read the next byte from the stream 124 * @return The next byte or -1 if the end of stream has been reached. 125 * @throws IOException If an IO problem occurs 126 * @see java.io.InputStream#read() 127 */ 128 @Override 129 public int read() throws IOException { 130 if (closed) { 131 throw new IOException("Attempted read from closed stream."); 132 } 133 134 if (pos >= contentLength) { 135 return -1; 136 } 137 final int b = this.in.read(); 138 if (b == -1) { 139 if (pos < contentLength) { 140 throw new ConnectionClosedException( 141 "Premature end of Content-Length delimited message body (expected: %,d; received: %,d)", 142 contentLength, pos); 143 } 144 } else { 145 pos++; 146 } 147 return b; 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 */ 162 @Override 163 public int read(final byte[] b, final int off, final int len) throws java.io.IOException { 164 if (closed) { 165 throw new IOException("Attempted read from closed stream."); 166 } 167 168 if (pos >= contentLength) { 169 return -1; 170 } 171 172 int chunk = len; 173 if (pos + len > contentLength) { 174 chunk = (int) (contentLength - pos); 175 } 176 final int readLen = this.in.read(b, off, chunk); 177 if (readLen == -1 && pos < contentLength) { 178 throw new ConnectionClosedException( 179 "Premature end of Content-Length delimited message body (expected: %,d; received: %,d)", 180 contentLength, pos); 181 } 182 if (readLen > 0) { 183 pos += readLen; 184 } 185 return readLen; 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 @Override 197 public int read(final byte[] b) throws IOException { 198 return read(b, 0, b.length); 199 } 200 201 /** 202 * Skips and discards a number of bytes from the input stream. 203 * @param n The number of bytes to skip. 204 * @return The actual number of bytes skipped. ≤ 0 if no bytes 205 * are skipped. 206 * @throws IOException If an error occurs while skipping bytes. 207 * @see InputStream#skip(long) 208 */ 209 @Override 210 public long skip(final long n) throws IOException { 211 if (n <= 0) { 212 return 0; 213 } 214 final byte[] buffer = new byte[BUFFER_SIZE]; 215 // make sure we don't skip more bytes than are 216 // still available 217 long remaining = Math.min(n, this.contentLength - this.pos); 218 // skip and keep track of the bytes actually skipped 219 long count = 0; 220 while (remaining > 0) { 221 final int readLen = read(buffer, 0, (int)Math.min(BUFFER_SIZE, remaining)); 222 if (readLen == -1) { 223 break; 224 } 225 count += readLen; 226 remaining -= readLen; 227 } 228 return count; 229 } 230 }