View Javadoc
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 }