View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v 1.16.2.1 2003/10/04 02:31:26 mbecke Exp $ 3 * $Revision: 1.16.2.1 $ 4 * $Date: 2003/10/04 02:31:26 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 2002-2003 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>. 59 * 60 * [Additional notices, if required by prior licensing conditions] 61 * 62 */ 63 64 package org.apache.commons.httpclient; 65 66 import java.io.ByteArrayOutputStream; 67 import java.io.IOException; 68 import java.io.InputStream; 69 70 71 /*** 72 * <p>Transparently coalesces chunks of a HTTP stream that uses 73 * Transfer-Encoding chunked.</p> 74 * 75 * <p>Note that this class NEVER closes the underlying stream, even when close 76 * gets called. Instead, it will read until the "end" of its chunking on close, 77 * which allows for the seamless invocation of subsequent HTTP 1.1 calls, while 78 * not requiring the client to remember to read the entire contents of the 79 * response.</p> 80 * 81 * @see ResponseInputStream 82 * 83 * @author Ortwin Gl�ck 84 * @author Sean C. Sullivan 85 * @author Martin Elwin 86 * @author Eric Johnson 87 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> 88 * @author Michael Becke 89 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> 90 * 91 * @since 2.0 92 * 93 */ 94 95 public class ChunkedInputStream extends InputStream { 96 /*** The inputstream that we're wrapping */ 97 private InputStream in; 98 99 /*** The chunk size */ 100 private int chunkSize; 101 102 /*** The current position within the current chunk */ 103 private int pos; 104 105 /*** True if we'are at the beginning of stream */ 106 private boolean bof = true; 107 108 /*** True if we've reached the end of stream */ 109 private boolean eof = false; 110 111 /*** True if this stream is closed */ 112 private boolean closed = false; 113 114 /*** The method that this stream came from */ 115 private HttpMethod method; 116 117 /*** 118 * 119 * 120 * @param in must be non-null 121 * @param method must be non-null 122 * 123 * @throws IOException If an IO error occurs 124 */ 125 public ChunkedInputStream( 126 final InputStream in, final HttpMethod method) throws IOException { 127 128 if (in == null) { 129 throw new IllegalArgumentException("InputStream parameter may not be null"); 130 } 131 if (method == null) { 132 throw new IllegalArgumentException("HttpMethod parameter may not be null"); 133 } 134 this.in = in; 135 this.method = method; 136 this.pos = 0; 137 } 138 139 /*** 140 * <p> Returns all the data in a chunked stream in coalesced form. A chunk 141 * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 142 * is detected.</p> 143 * 144 * <p> Trailer headers are read automcatically at the end of the stream and 145 * can be obtained with the getResponseFooters() method.</p> 146 * 147 * @return -1 of the end of the stream has been reached or the next data 148 * byte 149 * @throws IOException If an IO problem occurs 150 * 151 * @see HttpMethod#getResponseFooters() 152 */ 153 public int read() throws IOException { 154 155 if (closed) { 156 throw new IOException("Attempted read from closed stream."); 157 } 158 if (eof) { 159 return -1; 160 } 161 if (pos >= chunkSize) { 162 nextChunk(); 163 if (eof) { 164 return -1; 165 } 166 } 167 pos++; 168 return in.read(); 169 } 170 171 /*** 172 * Read some bytes from the stream. 173 * @param b The byte array that will hold the contents from the stream. 174 * @param off The offset into the byte array at which bytes will start to be 175 * placed. 176 * @param len the maximum number of bytes that can be returned. 177 * @return The number of bytes returned or -1 if the end of stream has been 178 * reached. 179 * @see java.io.InputStream#read(byte[], int, int) 180 * @throws IOException if an IO problem occurs. 181 */ 182 public int read (byte[] b, int off, int len) throws IOException { 183 184 if (closed) { 185 throw new IOException("Attempted read from closed stream."); 186 } 187 188 if (eof) { 189 return -1; 190 } 191 if (pos >= chunkSize) { 192 nextChunk(); 193 if (eof) { 194 return -1; 195 } 196 } 197 len = Math.min(len, chunkSize - pos); 198 int count = in.read(b, off, len); 199 pos += count; 200 return count; 201 } 202 203 /*** 204 * Read some bytes from the stream. 205 * @param b The byte array that will hold the contents from the stream. 206 * @return The number of bytes returned or -1 if the end of stream has been 207 * reached. 208 * @see java.io.InputStream#read(byte[]) 209 * @throws IOException if an IO problem occurs. 210 */ 211 public int read (byte[] b) throws IOException { 212 return read(b, 0, b.length); 213 } 214 215 /*** 216 * Read the CRLF terminator. 217 * @throws IOException If an IO error occurs. 218 */ 219 private void readCRLF() throws IOException { 220 int cr = in.read(); 221 int lf = in.read(); 222 if ((cr != '\r') || (lf != '\n')) { 223 throw new IOException( 224 "CRLF expected at end of chunk: " + cr + "/" + lf); 225 } 226 } 227 228 229 /*** 230 * Read the next chunk. 231 * @throws IOException If an IO error occurs. 232 */ 233 private void nextChunk() throws IOException { 234 if (!bof) { 235 readCRLF(); 236 } 237 chunkSize = getChunkSizeFromInputStream(in); 238 bof = false; 239 pos = 0; 240 if (chunkSize == 0) { 241 eof = true; 242 parseTrailerHeaders(); 243 } 244 } 245 246 /*** 247 * Expects the stream to start with a chunksize in hex with optional 248 * comments after a semicolon. The line must end with a CRLF: "a3; some 249 * comment\r\n" Positions the stream at the start of the next line. 250 * 251 * @param in The new input stream. 252 * 253 * @return the chunk size as integer 254 * 255 * @throws IOException when the chunk size could not be parsed 256 */ 257 private static int getChunkSizeFromInputStream(final InputStream in) 258 throws IOException { 259 260 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 261 // States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end 262 int state = 0; 263 while (state != -1) { 264 int b = in.read(); 265 if (b == -1) { 266 throw new IOException("chunked stream ended unexpectedly"); 267 } 268 switch (state) { 269 case 0: 270 switch (b) { 271 case '\r': 272 state = 1; 273 break; 274 case '\"': 275 state = 2; 276 /* fall through */ 277 default: 278 baos.write(b); 279 } 280 break; 281 282 case 1: 283 if (b == '\n') { 284 state = -1; 285 } else { 286 // this was not CRLF 287 throw new IOException("Protocol violation: Unexpected" 288 + " single newline character in chunk size"); 289 } 290 break; 291 292 case 2: 293 switch (b) { 294 case '//': 295 b = in.read(); 296 baos.write(b); 297 break; 298 case '\"': 299 state = 0; 300 /* fall through */ 301 default: 302 baos.write(b); 303 } 304 break; 305 default: throw new RuntimeException("assertion failed"); 306 } 307 } 308 309 //parse data 310 String dataString = HttpConstants.getString(baos.toByteArray()); 311 int separator = dataString.indexOf(';'); 312 dataString = (separator > 0) 313 ? dataString.substring(0, separator).trim() 314 : dataString.trim(); 315 316 int result; 317 try { 318 result = Integer.parseInt(dataString.trim(), 16); 319 } catch (NumberFormatException e) { 320 throw new IOException ("Bad chunk size: " + dataString); 321 } 322 return result; 323 } 324 325 /*** 326 * Reads and stores the Trailer headers. 327 * @throws IOException If an IO problem occurs 328 */ 329 private void parseTrailerHeaders() throws IOException { 330 Header[] footers = HttpParser.parseHeaders(in); 331 332 for (int i = 0; i < footers.length; i++) { 333 method.addResponseFooter(footers[i]); 334 } 335 } 336 337 /*** 338 * Upon close, this reads the remainder of the chunked message, 339 * leaving the underlying socket at a position to start reading the 340 * next response without scanning. 341 * @throws IOException If an IO problem occurs. 342 */ 343 public void close() throws IOException { 344 if (!closed) { 345 try { 346 if (!eof) { 347 exhaustInputStream(this); 348 } 349 } finally { 350 eof = true; 351 closed = true; 352 } 353 } 354 } 355 356 /*** 357 * Exhaust an input stream, reading until EOF has been encountered. 358 * 359 * <p>Note that this function is intended as a non-public utility. 360 * This is a little weird, but it seemed silly to make a utility 361 * class for this one function, so instead it is just static and 362 * shared that way.</p> 363 * 364 * @param inStream The {@link InputStream} to exhaust. 365 * @throws IOException If an IO problem occurs 366 */ 367 static void exhaustInputStream(InputStream inStream) throws IOException { 368 // read and discard the remainder of the message 369 byte buffer[] = new byte[1024]; 370 while (inStream.read(buffer) >= 0) { 371 ; 372 } 373 } 374 }

This page was automatically generated by Maven