001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.io; 029 030import java.io.IOException; 031import java.io.OutputStream; 032 033import org.apache.http.io.SessionOutputBuffer; 034 035/** 036 * Implements chunked transfer coding. The content is sent in small chunks. 037 * Entities transferred using this output stream can be of unlimited length. 038 * Writes are buffered to an internal buffer (2048 default size). 039 * <p> 040 * Note that this class NEVER closes the underlying stream, even when close 041 * gets called. Instead, the stream will be marked as closed and no further 042 * output will be permitted. 043 * 044 * 045 * @since 4.0 046 */ 047public class ChunkedOutputStream extends OutputStream { 048 049 // ----------------------------------------------------- Instance Variables 050 private final SessionOutputBuffer out; 051 052 private final byte[] cache; 053 054 private int cachePosition = 0; 055 056 private boolean wroteLastChunk = false; 057 058 /** True if the stream is closed. */ 059 private boolean closed = false; 060 061 /** 062 * Wraps a session output buffer and chunk-encodes the output. 063 * 064 * @param out The session output buffer 065 * @param bufferSize The minimum chunk size (excluding last chunk) 066 * @throws IOException not thrown 067 * 068 * @deprecated (4.3) use {@link ChunkedOutputStream#ChunkedOutputStream(int, SessionOutputBuffer)} 069 */ 070 @Deprecated 071 public ChunkedOutputStream(final SessionOutputBuffer out, final int bufferSize) 072 throws IOException { 073 this(bufferSize, out); 074 } 075 076 /** 077 * Wraps a session output buffer and chunks the output. The default buffer 078 * size of 2048 was chosen because the chunk overhead is less than 0.5% 079 * 080 * @param out the output buffer to wrap 081 * @throws IOException not thrown 082 * 083 * @deprecated (4.3) use {@link ChunkedOutputStream#ChunkedOutputStream(int, SessionOutputBuffer)} 084 */ 085 @Deprecated 086 public ChunkedOutputStream(final SessionOutputBuffer out) 087 throws IOException { 088 this(2048, out); 089 } 090 091 /** 092 * Wraps a session output buffer and chunk-encodes the output. 093 * 094 * @param bufferSize The minimum chunk size (excluding last chunk) 095 * @param out The session output buffer 096 */ 097 public ChunkedOutputStream(final int bufferSize, final SessionOutputBuffer out) { 098 super(); 099 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}