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.OutputStream; 32 33 import org.apache.http.io.SessionOutputBuffer; 34 35 /** 36 * Implements chunked transfer coding. The content is sent in small chunks. 37 * Entities transferred using this output stream can be of unlimited length. 38 * Writes are buffered to an internal buffer (2048 default size). 39 * <p> 40 * Note that this class NEVER closes the underlying stream, even when close 41 * gets called. Instead, the stream will be marked as closed and no further 42 * output will be permitted. 43 * 44 * 45 * @since 4.0 46 */ 47 public class ChunkedOutputStream extends OutputStream { 48 49 // ----------------------------------------------------- Instance Variables 50 private final SessionOutputBuffer out; 51 52 private final byte[] cache; 53 54 private int cachePosition = 0; 55 56 private boolean wroteLastChunk = false; 57 58 /** True if the stream is closed. */ 59 private boolean closed = false; 60 61 /** 62 * Wraps a session output buffer and chunk-encodes the output. 63 * 64 * @param out The session output buffer 65 * @param bufferSize The minimum chunk size (excluding last chunk) 66 * @throws IOException not thrown 67 * 68 * @deprecated (4.3) use {@link ChunkedOutputStream#ChunkedOutputStream(int, SessionOutputBuffer)} 69 */ 70 @Deprecated 71 public ChunkedOutputStream(final SessionOutputBuffer out, final int bufferSize) 72 throws IOException { 73 this(bufferSize, out); 74 } 75 76 /** 77 * Wraps a session output buffer and chunks the output. The default buffer 78 * size of 2048 was chosen because the chunk overhead is less than 0.5% 79 * 80 * @param out the output buffer to wrap 81 * @throws IOException not thrown 82 * 83 * @deprecated (4.3) use {@link ChunkedOutputStream#ChunkedOutputStream(int, SessionOutputBuffer)} 84 */ 85 @Deprecated 86 public ChunkedOutputStream(final SessionOutputBuffer out) 87 throws IOException { 88 this(2048, out); 89 } 90 91 /** 92 * Wraps a session output buffer and chunk-encodes the output. 93 * 94 * @param bufferSize The minimum chunk size (excluding last chunk) 95 * @param out The session output buffer 96 */ 97 public ChunkedOutputStream(final int bufferSize, final SessionOutputBuffer out) { 98 super(); 99 this.cache = new byte[bufferSize]; 100 this.out = out; 101 } 102 103 /** 104 * Writes the cache out onto the underlying stream 105 */ 106 protected void flushCache() throws IOException { 107 if (this.cachePosition > 0) { 108 this.out.writeLine(Integer.toHexString(this.cachePosition)); 109 this.out.write(this.cache, 0, this.cachePosition); 110 this.out.writeLine(""); 111 this.cachePosition = 0; 112 } 113 } 114 115 /** 116 * Writes the cache and bufferToAppend to the underlying stream 117 * as one large chunk 118 */ 119 protected void flushCacheWithAppend(final byte bufferToAppend[], final int off, final int len) throws IOException { 120 this.out.writeLine(Integer.toHexString(this.cachePosition + len)); 121 this.out.write(this.cache, 0, this.cachePosition); 122 this.out.write(bufferToAppend, off, len); 123 this.out.writeLine(""); 124 this.cachePosition = 0; 125 } 126 127 protected void writeClosingChunk() throws IOException { 128 // Write the final chunk. 129 this.out.writeLine("0"); 130 this.out.writeLine(""); 131 } 132 133 // ----------------------------------------------------------- Public Methods 134 /** 135 * Must be called to ensure the internal cache is flushed and the closing 136 * chunk is written. 137 * @throws IOException in case of an I/O error 138 */ 139 public void finish() throws IOException { 140 if (!this.wroteLastChunk) { 141 flushCache(); 142 writeClosingChunk(); 143 this.wroteLastChunk = true; 144 } 145 } 146 147 // -------------------------------------------- OutputStream Methods 148 @Override 149 public void write(final int b) throws IOException { 150 if (this.closed) { 151 throw new IOException("Attempted write to closed stream."); 152 } 153 this.cache[this.cachePosition] = (byte) b; 154 this.cachePosition++; 155 if (this.cachePosition == this.cache.length) { 156 flushCache(); 157 } 158 } 159 160 /** 161 * Writes the array. If the array does not fit within the buffer, it is 162 * not split, but rather written out as one large chunk. 163 */ 164 @Override 165 public void write(final byte b[]) throws IOException { 166 write(b, 0, b.length); 167 } 168 169 /** 170 * Writes the array. If the array does not fit within the buffer, it is 171 * not split, but rather written out as one large chunk. 172 */ 173 @Override 174 public void write(final byte src[], final int off, final int len) throws IOException { 175 if (this.closed) { 176 throw new IOException("Attempted write to closed stream."); 177 } 178 if (len >= this.cache.length - this.cachePosition) { 179 flushCacheWithAppend(src, off, len); 180 } else { 181 System.arraycopy(src, off, cache, this.cachePosition, len); 182 this.cachePosition += len; 183 } 184 } 185 186 /** 187 * Flushes the content buffer and the underlying stream. 188 */ 189 @Override 190 public void flush() throws IOException { 191 flushCache(); 192 this.out.flush(); 193 } 194 195 /** 196 * Finishes writing to the underlying stream, but does NOT close the underlying stream. 197 */ 198 @Override 199 public void close() throws IOException { 200 if (!this.closed) { 201 this.closed = true; 202 finish(); 203 this.out.flush(); 204 } 205 } 206 }