001: /*
002: * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0-beta1/module-main/src/main/java/org/apache/http/impl/io/ChunkedOutputStream.java $
003: * $Revision: 567360 $
004: * $Date: 2007-08-19 08:49:21 +0200 (Sun, 19 Aug 2007) $
005: *
006: * ====================================================================
007: * Licensed to the Apache Software Foundation (ASF) under one
008: * or more contributor license agreements. See the NOTICE file
009: * distributed with this work for additional information
010: * regarding copyright ownership. The ASF licenses this file
011: * to you under the Apache License, Version 2.0 (the
012: * "License"); you may not use this file except in compliance
013: * with the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing,
018: * software distributed under the License is distributed on an
019: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020: * KIND, either express or implied. See the License for the
021: * specific language governing permissions and limitations
022: * under the License.
023: * ====================================================================
024: *
025: * This software consists of voluntary contributions made by many
026: * individuals on behalf of the Apache Software Foundation. For more
027: * information on the Apache Software Foundation, please see
028: * <http://www.apache.org/>.
029: *
030: */
031:
032: package org.apache.http.impl.io;
033:
034: import java.io.IOException;
035: import java.io.OutputStream;
036:
037: import org.apache.http.io.SessionOutputBuffer;
038:
039: /**
040: * Implements chunked transfer coding.
041: * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>,
042: * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>.
043: * Writes are buffered to an internal buffer (2048 default size).
044: * Chunks are guaranteed to be at least as large as the buffer size
045: * (except for the last chunk).
046: *
047: * @author Mohammad Rezaei (Goldman, Sachs & Co.)
048: * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
049: *
050: * @since 4.0
051: */
052: public class ChunkedOutputStream extends OutputStream {
053:
054: // ----------------------------------------------------- Instance Variables
055: private final SessionOutputBuffer out;
056:
057: private byte[] cache;
058:
059: private int cachePosition = 0;
060:
061: private boolean wroteLastChunk = false;
062:
063: /** True if the stream is closed. */
064: private boolean closed = false;
065:
066: // ----------------------------------------------------------- Constructors
067: /**
068: * Wraps a session output buffer and chunks the output.
069: * @param out the session output buffer to wrap
070: * @param bufferSize minimum chunk size (excluding last chunk)
071: * @throws IOException
072: */
073: public ChunkedOutputStream(final SessionOutputBuffer out,
074: int bufferSize) throws IOException {
075: super ();
076: this .cache = new byte[bufferSize];
077: this .out = out;
078: }
079:
080: /**
081: * Wraps a session output buffer and chunks the output. The default buffer
082: * size of 2048 was chosen because the chunk overhead is less than 0.5%
083: *
084: * @param out the output buffer to wrap
085: * @throws IOException
086: */
087: public ChunkedOutputStream(final SessionOutputBuffer out)
088: throws IOException {
089: this (out, 2048);
090: }
091:
092: // ----------------------------------------------------------- Internal methods
093: /**
094: * Writes the cache out onto the underlying stream
095: * @throws IOException
096: */
097: protected void flushCache() throws IOException {
098: if (this .cachePosition > 0) {
099: this .out.writeLine(Integer.toHexString(this .cachePosition));
100: this .out.write(this .cache, 0, this .cachePosition);
101: this .out.writeLine("");
102: this .cachePosition = 0;
103: }
104: }
105:
106: /**
107: * Writes the cache and bufferToAppend to the underlying stream
108: * as one large chunk
109: * @param bufferToAppend
110: * @param off
111: * @param len
112: * @throws IOException
113: */
114: protected void flushCacheWithAppend(byte bufferToAppend[], int off,
115: int len) throws IOException {
116: this .out.writeLine(Integer
117: .toHexString(this .cachePosition + len));
118: this .out.write(this .cache, 0, this .cachePosition);
119: this .out.write(bufferToAppend, off, len);
120: this .out.writeLine("");
121: this .cachePosition = 0;
122: }
123:
124: protected void writeClosingChunk() throws IOException {
125: // Write the final chunk.
126: this .out.writeLine("0");
127: this .out.writeLine("");
128: }
129:
130: // ----------------------------------------------------------- Public Methods
131: /**
132: * Must be called to ensure the internal cache is flushed and the closing chunk is written.
133: * @throws IOException
134: */
135: public void finish() throws IOException {
136: if (!this .wroteLastChunk) {
137: flushCache();
138: writeClosingChunk();
139: this .wroteLastChunk = true;
140: }
141: }
142:
143: // -------------------------------------------- OutputStream Methods
144: public void write(int b) throws IOException {
145: if (this .closed) {
146: throw new IOException("Attempted write to closed stream.");
147: }
148: this .cache[this .cachePosition] = (byte) b;
149: this .cachePosition++;
150: if (this .cachePosition == this .cache.length)
151: flushCache();
152: }
153:
154: /**
155: * Writes the array. If the array does not fit within the buffer, it is
156: * not split, but rather written out as one large chunk.
157: * @param b
158: * @throws IOException
159: */
160: public void write(byte b[]) throws IOException {
161: write(b, 0, b.length);
162: }
163:
164: public void write(byte src[], int off, int len) throws IOException {
165: if (this .closed) {
166: throw new IOException("Attempted write to closed stream.");
167: }
168: if (len >= this .cache.length - this .cachePosition) {
169: flushCacheWithAppend(src, off, len);
170: } else {
171: System.arraycopy(src, off, cache, this .cachePosition, len);
172: this .cachePosition += len;
173: }
174: }
175:
176: /**
177: * Flushes the underlying stream, but leaves the internal buffer alone.
178: * @throws IOException
179: */
180: public void flush() throws IOException {
181: this .out.flush();
182: }
183:
184: /**
185: * Finishes writing to the underlying stream, but does NOT close the underlying stream.
186: * @throws IOException
187: */
188: public void close() throws IOException {
189: if (!this .closed) {
190: this .closed = true;
191: finish();
192: this.out.flush();
193: }
194: }
195: }
|