1 /* 2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java $ 3 * $Revision: 645081 $ 4 * $Date: 2008-04-05 04:36:42 -0700 (Sat, 05 Apr 2008) $ 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 * 25 * This software consists of voluntary contributions made by many 26 * individuals on behalf of the Apache Software Foundation. For more 27 * information on the Apache Software Foundation, please see 28 * <http://www.apache.org/>. 29 * 30 */ 31 32 package org.apache.http.impl.io; 33 34 import java.io.IOException; 35 import java.io.OutputStream; 36 37 import org.apache.http.io.SessionOutputBuffer; 38 39 /** 40 * Implements chunked transfer coding. 41 * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, 42 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>. 43 * Writes are buffered to an internal buffer (2048 default size). 44 * 45 * @author Mohammad Rezaei (Goldman, Sachs & Co.) 46 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> 47 * 48 * @since 4.0 49 */ 50 public class ChunkedOutputStream extends OutputStream { 51 52 // ----------------------------------------------------- Instance Variables 53 private final SessionOutputBuffer out; 54 55 private byte[] cache; 56 57 private int cachePosition = 0; 58 59 private boolean wroteLastChunk = false; 60 61 /** True if the stream is closed. */ 62 private boolean closed = false; 63 64 // ----------------------------------------------------------- Constructors 65 /** 66 * Wraps a session output buffer and chunks the output. 67 * @param out the session output buffer to wrap 68 * @param bufferSize minimum chunk size (excluding last chunk) 69 * @throws IOException 70 */ 71 public ChunkedOutputStream(final SessionOutputBuffer out, int bufferSize) 72 throws IOException { 73 super(); 74 this.cache = new byte[bufferSize]; 75 this.out = out; 76 } 77 78 /** 79 * Wraps a session output buffer and chunks the output. The default buffer 80 * size of 2048 was chosen because the chunk overhead is less than 0.5% 81 * 82 * @param out the output buffer to wrap 83 * @throws IOException 84 */ 85 public ChunkedOutputStream(final SessionOutputBuffer out) 86 throws IOException { 87 this(out, 2048); 88 } 89 90 // ----------------------------------------------------------- Internal methods 91 /** 92 * Writes the cache out onto the underlying stream 93 * @throws IOException 94 */ 95 protected void flushCache() throws IOException { 96 if (this.cachePosition > 0) { 97 this.out.writeLine(Integer.toHexString(this.cachePosition)); 98 this.out.write(this.cache, 0, this.cachePosition); 99 this.out.writeLine(""); 100 this.cachePosition = 0; 101 } 102 } 103 104 /** 105 * Writes the cache and bufferToAppend to the underlying stream 106 * as one large chunk 107 * @param bufferToAppend 108 * @param off 109 * @param len 110 * @throws IOException 111 */ 112 protected void flushCacheWithAppend(byte bufferToAppend[], int off, int len) throws IOException { 113 this.out.writeLine(Integer.toHexString(this.cachePosition + len)); 114 this.out.write(this.cache, 0, this.cachePosition); 115 this.out.write(bufferToAppend, off, len); 116 this.out.writeLine(""); 117 this.cachePosition = 0; 118 } 119 120 protected void writeClosingChunk() throws IOException { 121 // Write the final chunk. 122 this.out.writeLine("0"); 123 this.out.writeLine(""); 124 } 125 126 // ----------------------------------------------------------- Public Methods 127 /** 128 * Must be called to ensure the internal cache is flushed and the closing chunk is written. 129 * @throws IOException 130 */ 131 public void finish() throws IOException { 132 if (!this.wroteLastChunk) { 133 flushCache(); 134 writeClosingChunk(); 135 this.wroteLastChunk = true; 136 } 137 } 138 139 // -------------------------------------------- OutputStream Methods 140 public void write(int b) throws IOException { 141 if (this.closed) { 142 throw new IOException("Attempted write to closed stream."); 143 } 144 this.cache[this.cachePosition] = (byte) b; 145 this.cachePosition++; 146 if (this.cachePosition == this.cache.length) flushCache(); 147 } 148 149 /** 150 * Writes the array. If the array does not fit within the buffer, it is 151 * not split, but rather written out as one large chunk. 152 * @param b 153 * @throws IOException 154 */ 155 public void write(byte b[]) throws IOException { 156 write(b, 0, b.length); 157 } 158 159 public void write(byte src[], int off, int len) throws IOException { 160 if (this.closed) { 161 throw new IOException("Attempted write to closed stream."); 162 } 163 if (len >= this.cache.length - this.cachePosition) { 164 flushCacheWithAppend(src, off, len); 165 } else { 166 System.arraycopy(src, off, cache, this.cachePosition, len); 167 this.cachePosition += len; 168 } 169 } 170 171 /** 172 * Flushes the content buffer and the underlying stream. 173 * @throws IOException 174 */ 175 public void flush() throws IOException { 176 flushCache(); 177 this.out.flush(); 178 } 179 180 /** 181 * Finishes writing to the underlying stream, but does NOT close the underlying stream. 182 * @throws IOException 183 */ 184 public void close() throws IOException { 185 if (!this.closed) { 186 this.closed = true; 187 finish(); 188 this.out.flush(); 189 } 190 } 191 } 192