View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v 1.16 2003/05/08 17:33:51 olegk Exp $ 3 * $Revision: 1.16 $ 4 * $Date: 2003/05/08 17:33:51 $ 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 * @param required <tt>true<tt/> if a valid chunk must be present, 253 * <tt>false<tt/> otherwise. 254 * 255 * @return the chunk size as integer 256 * 257 * @throws IOException when the chunk size could not be parsed 258 */ 259 private static int getChunkSizeFromInputStream(final InputStream in) 260 throws IOException { 261 262 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 263 // States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end 264 int state = 0; 265 while (state != -1) { 266 int b = in.read(); 267 if (b == -1) { 268 throw new IOException("chunked stream ended unexpectedly"); 269 } 270 switch (state) { 271 case 0: 272 switch (b) { 273 case '\r': 274 state = 1; 275 break; 276 case '\"': 277 state = 2; 278 /* fall through */ 279 default: 280 baos.write(b); 281 } 282 break; 283 284 case 1: 285 if (b == '\n') { 286 state = -1; 287 } else { 288 // this was not CRLF 289 throw new IOException("Protocol violation: Unexpected" 290 + " single newline character in chunk size"); 291 } 292 break; 293 294 case 2: 295 switch (b) { 296 case '//': 297 b = in.read(); 298 baos.write(b); 299 break; 300 case '\"': 301 state = 0; 302 /* fall through */ 303 default: 304 baos.write(b); 305 } 306 break; 307 default: throw new RuntimeException("assertion failed"); 308 } 309 } 310 311 //parse data 312 String dataString = HttpConstants.getString(baos.toByteArray()); 313 int separator = dataString.indexOf(';'); 314 dataString = (separator > 0) 315 ? dataString.substring(0, separator).trim() 316 : dataString.trim(); 317 318 int result; 319 try { 320 result = Integer.parseInt(dataString.trim(), 16); 321 } catch (NumberFormatException e) { 322 throw new IOException ("Bad chunk size: " + dataString); 323 } 324 return result; 325 } 326 327 /*** 328 * Reads and stores the Trailer headers. 329 * @throws IOException If an IO problem occurs 330 */ 331 private void parseTrailerHeaders() throws IOException { 332 Header[] footers = HttpParser.parseHeaders(in); 333 334 for (int i = 0; i < footers.length; i++) { 335 method.addResponseFooter(footers[i]); 336 } 337 } 338 339 /*** 340 * Upon close, this reads the remainder of the chunked message, 341 * leaving the underlying socket at a position to start reading the 342 * next response without scanning. 343 * @throws IOException If an IO problem occurs. 344 */ 345 public void close() throws IOException { 346 if (!closed) { 347 try { 348 if (!eof) { 349 exhaustInputStream(this); 350 } 351 } finally { 352 eof = true; 353 closed = true; 354 } 355 } 356 } 357 358 /*** 359 * Exhaust an input stream, reading until EOF has been encountered. 360 * 361 * <p>Note that this function is intended as a non-public utility. 362 * This is a little weird, but it seemed silly to make a utility 363 * class for this one function, so instead it is just static and 364 * shared that way.</p> 365 * 366 * @param inStream The {@link InputStream} to exhaust. 367 * @throws IOException If an IO problem occurs 368 */ 369 static void exhaustInputStream(InputStream inStream) throws IOException { 370 // read and discard the remainder of the message 371 byte buffer[] = new byte[1024]; 372 while (inStream.read(buffer) >= 0) { 373 ; 374 } 375 } 376 }

This page was automatically generated by Maven