• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &amp; 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      */
ChunkedOutputStream(final SessionOutputBuffer out, int bufferSize)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      */
ChunkedOutputStream(final SessionOutputBuffer out)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      */
flushCache()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      */
flushCacheWithAppend(byte bufferToAppend[], int off, int len)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 
writeClosingChunk()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      */
finish()131     public void finish() throws IOException {
132         if (!this.wroteLastChunk) {
133             flushCache();
134             writeClosingChunk();
135             this.wroteLastChunk = true;
136         }
137     }
138 
139     // -------------------------------------------- OutputStream Methods
write(int b)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      */
write(byte b[])155     public void write(byte b[]) throws IOException {
156         write(b, 0, b.length);
157     }
158 
write(byte src[], int off, int len)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      */
flush()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      */
close()184     public void close() throws IOException {
185         if (!this.closed) {
186             this.closed = true;
187             finish();
188             this.out.flush();
189         }
190     }
191 }
192