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