1 /* 2 * $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/httpcore/tags/4.0-alpha2/src/java/org/apache/http/io/ChunkedOutputStream.java $ 3 * $Revision: 390883 $ 4 * $Date: 2006-04-02 20:39:50 +0200 (Sun, 02 Apr 2006) $ 5 * 6 * ==================================================================== 7 * 8 * Copyright 2002-2004 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.OutputStream; 34 35 /** 36 * <p>This class implements chunked transfer coding as described in the 37 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">Section 3.6.1</a> 38 * of <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>. 39 * Writes are buffered to an internal buffer (2048 default size). Chunks are guaranteed 40 * to be at least as large as the buffer size (except for the last chunk).</p> 41 * 42 * <h>3.6.1 Chunked Transfer Coding</h> 43 * <p> 44 * The chunked encoding modifies the body of a message in order to transfer it as a series 45 * of chunks, each with its own size indicator, followed by an OPTIONAL trailer containing 46 * entity-header fields. This allows dynamically produced content to be transferred along 47 * with the information necessary for the recipient to verify that it has received the full 48 * message. 49 * </p> 50 * <pre> 51 * Chunked-Body = *chunk 52 * last-chunk 53 * trailer 54 * CRLF 55 * 56 * chunk = chunk-size [ chunk-extension ] CRLF 57 * chunk-data CRLF 58 * chunk-size = 1*HEX 59 * last-chunk = 1*("0") [ chunk-extension ] CRLF 60 * 61 * chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) 62 * chunk-ext-name = token 63 * chunk-ext-val = token | quoted-string 64 * chunk-data = chunk-size(OCTET) 65 * trailer = *(entity-header CRLF) 66 * </pre> 67 * <p> 68 * The chunk-size field is a string of hex digits indicating the size of the chunk. The 69 * chunked encoding is ended by any chunk whose size is zero, followed by the trailer, 70 * which is terminated by an empty line. 71 * </p> 72 * <p> 73 * The trailer allows the sender to include additional HTTP header fields at the end 74 * of the message. The Trailer header field can be used to indicate which header fields 75 * are included in a trailer (see section 14.40). 76 * </p> 77 * <p> 78 * A server using chunked transfer-coding in a response MUST NOT use the trailer for any 79 * header fields unless at least one of the following is true: 80 * </p> 81 * <p> 82 * a)the request included a TE header field that indicates "trailers" is acceptable in 83 * the transfer-coding of the response, as described in section 14.39; or, 84 * </p> 85 * <p> 86 * b)the server is the origin server for the response, the trailer fields consist entirely 87 * of optional metadata, and the recipient could use the message (in a manner acceptable 88 * to the origin server) without receiving this metadata. In other words, the origin server 89 * is willing to accept the possibility that the trailer fields might be silently discarded 90 * along the path to the client. 91 * </p> 92 * <p> 93 * This requirement prevents an interoperability failure when the message is being received 94 * by an HTTP/1.1 (or later) proxy and forwarded to an HTTP/1.0 recipient. It avoids a 95 * situation where compliance with the protocol would have necessitated a possibly infinite 96 * buffer on the proxy. 97 * </p> 98 * 99 * @author Mohammad Rezaei, Goldman, Sachs & Co. 100 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> 101 */ 102 public class ChunkedOutputStream extends OutputStream { 103 104 // ----------------------------------------------------- Instance Variables 105 private final HttpDataTransmitter out; 106 107 private byte[] cache; 108 109 private int cachePosition = 0; 110 111 private boolean wroteLastChunk = false; 112 113 /** True if the stream is closed. */ 114 private boolean closed = false; 115 116 // ----------------------------------------------------------- Constructors 117 /** 118 * Wraps a stream and chunks the output. 119 * @param out the transmitter to wrap 120 * @param bufferSize minimum chunk size (excluding last chunk) 121 * @throws IOException 122 * 123 * @since 3.0 124 */ 125 public ChunkedOutputStream(final HttpDataTransmitter out, int bufferSize) 126 throws IOException { 127 super(); 128 this.cache = new byte[bufferSize]; 129 this.out = out; 130 } 131 132 /** 133 * Wraps a data transmitter and chunks the output. The default buffer size of 2048 was 134 * chosen because the chunk overhead is less than 0.5% 135 * @param datatransmitter the transmitter to wrap 136 * @throws IOException 137 */ 138 public ChunkedOutputStream(final HttpDataTransmitter datatransmitter) 139 throws IOException { 140 this(datatransmitter, 2048); 141 } 142 143 // ----------------------------------------------------------- Internal methods 144 /** 145 * Writes the cache out onto the underlying stream 146 * @throws IOException 147 * 148 * @since 3.0 149 */ 150 protected void flushCache() throws IOException { 151 if (this.cachePosition > 0) { 152 this.out.writeLine(Integer.toHexString(this.cachePosition)); 153 this.out.write(this.cache, 0, this.cachePosition); 154 this.out.writeLine(""); 155 this.cachePosition = 0; 156 } 157 } 158 159 /** 160 * Writes the cache and bufferToAppend to the underlying stream 161 * as one large chunk 162 * @param bufferToAppend 163 * @param off 164 * @param len 165 * @throws IOException 166 * 167 * @since 3.0 168 */ 169 protected void flushCacheWithAppend(byte bufferToAppend[], int off, int len) throws IOException { 170 this.out.writeLine(Integer.toHexString(this.cachePosition + len)); 171 this.out.write(this.cache, 0, this.cachePosition); 172 this.out.write(bufferToAppend, off, len); 173 this.out.writeLine(""); 174 this.cachePosition = 0; 175 } 176 177 protected void writeClosingChunk() throws IOException { 178 // Write the final chunk. 179 this.out.writeLine("0"); 180 this.out.writeLine(""); 181 } 182 183 // ----------------------------------------------------------- Public Methods 184 /** 185 * Must be called to ensure the internal cache is flushed and the closing chunk is written. 186 * @throws IOException 187 * 188 * @since 3.0 189 */ 190 public void finish() throws IOException { 191 if (!this.wroteLastChunk) { 192 flushCache(); 193 writeClosingChunk(); 194 this.wroteLastChunk = true; 195 } 196 } 197 198 // -------------------------------------------- OutputStream Methods 199 public void write(int b) throws IOException { 200 if (this.closed) { 201 throw new IOException("Attempted write to closed stream."); 202 } 203 this.cache[this.cachePosition] = (byte) b; 204 this.cachePosition++; 205 if (this.cachePosition == this.cache.length) flushCache(); 206 } 207 208 /** 209 * Writes the array. If the array does not fit within the buffer, it is 210 * not split, but rather written out as one large chunk. 211 * @param b 212 * @throws IOException 213 * 214 * @since 3.0 215 */ 216 public void write(byte b[]) throws IOException { 217 write(b, 0, b.length); 218 } 219 220 public void write(byte src[], int off, int len) throws IOException { 221 if (this.closed) { 222 throw new IOException("Attempted write to closed stream."); 223 } 224 if (len >= this.cache.length - this.cachePosition) { 225 flushCacheWithAppend(src, off, len); 226 } else { 227 System.arraycopy(src, off, cache, this.cachePosition, len); 228 this.cachePosition += len; 229 } 230 } 231 232 /** 233 * Flushes the underlying stream, but leaves the internal buffer alone. 234 * @throws IOException 235 */ 236 public void flush() throws IOException { 237 this.out.flush(); 238 } 239 240 /** 241 * Finishes writing to the underlying stream, but does NOT close the underlying stream. 242 * @throws IOException 243 */ 244 public void close() throws IOException { 245 if (!this.closed) { 246 this.closed = true; 247 finish(); 248 this.out.flush(); 249 } 250 } 251 }