001: /*
002: * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/ChunkedOutputStream.java,v 1.16 2004/05/13 04:03:25 mbecke Exp $
003: * $Revision: 480424 $
004: * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
005: *
006: * ====================================================================
007: *
008: * Licensed to the Apache Software Foundation (ASF) under one or more
009: * contributor license agreements. See the NOTICE file distributed with
010: * this work for additional information regarding copyright ownership.
011: * The ASF licenses this file to You under the Apache License, Version 2.0
012: * (the "License"); you may not use this file except in compliance with
013: * 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, software
018: * distributed under the License is distributed on an "AS IS" BASIS,
019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020: * See the License for the specific language governing permissions and
021: * limitations under the License.
022: * ====================================================================
023: *
024: * This software consists of voluntary contributions made by many
025: * individuals on behalf of the Apache Software Foundation. For more
026: * information on the Apache Software Foundation, please see
027: * <http://www.apache.org/>.
028: *
029: */
030:
031: package org.apache.commons.httpclient;
032:
033: import java.io.IOException;
034: import java.io.OutputStream;
035:
036: import org.apache.commons.httpclient.util.EncodingUtil;
037:
038: /**
039: * Implements HTTP chunking support. Writes are buffered to an internal buffer (2048 default size).
040: * Chunks are guaranteed to be at least as large as the buffer size (except for the last chunk).
041: *
042: * @author Mohammad Rezaei, Goldman, Sachs & Co.
043: */
044: public class ChunkedOutputStream extends OutputStream {
045:
046: // ------------------------------------------------------- Static Variables
047: private static final byte CRLF[] = new byte[] { (byte) 13,
048: (byte) 10 };
049:
050: /** End chunk */
051: private static final byte ENDCHUNK[] = CRLF;
052:
053: /** 0 */
054: private static final byte ZERO[] = new byte[] { (byte) '0' };
055:
056: // ----------------------------------------------------- Instance Variables
057: private OutputStream stream = null;
058:
059: private byte[] cache;
060:
061: private int cachePosition = 0;
062:
063: private boolean wroteLastChunk = false;
064:
065: // ----------------------------------------------------------- Constructors
066: /**
067: * Wraps a stream and chunks the output.
068: * @param stream to wrap
069: * @param bufferSize minimum chunk size (excluding last chunk)
070: * @throws IOException
071: *
072: * @since 3.0
073: */
074: public ChunkedOutputStream(OutputStream stream, int bufferSize)
075: throws IOException {
076: this .cache = new byte[bufferSize];
077: this .stream = stream;
078: }
079:
080: /**
081: * Wraps a stream and chunks the output. The default buffer size of 2048 was chosen because
082: * the chunk overhead is less than 0.5%
083: * @param stream
084: * @throws IOException
085: */
086: public ChunkedOutputStream(OutputStream stream) throws IOException {
087: this (stream, 2048);
088: }
089:
090: // ----------------------------------------------------------- Internal methods
091: /**
092: * Writes the cache out onto the underlying stream
093: * @throws IOException
094: *
095: * @since 3.0
096: */
097: protected void flushCache() throws IOException {
098: if (cachePosition > 0) {
099: byte chunkHeader[] = EncodingUtil.getAsciiBytes(Integer
100: .toHexString(cachePosition)
101: + "\r\n");
102: stream.write(chunkHeader, 0, chunkHeader.length);
103: stream.write(cache, 0, cachePosition);
104: stream.write(ENDCHUNK, 0, ENDCHUNK.length);
105: cachePosition = 0;
106: }
107: }
108:
109: /**
110: * Writes the cache and bufferToAppend to the underlying stream
111: * as one large chunk
112: * @param bufferToAppend
113: * @param off
114: * @param len
115: * @throws IOException
116: *
117: * @since 3.0
118: */
119: protected void flushCacheWithAppend(byte bufferToAppend[], int off,
120: int len) throws IOException {
121: byte chunkHeader[] = EncodingUtil.getAsciiBytes(Integer
122: .toHexString(cachePosition + len)
123: + "\r\n");
124: stream.write(chunkHeader, 0, chunkHeader.length);
125: stream.write(cache, 0, cachePosition);
126: stream.write(bufferToAppend, off, len);
127: stream.write(ENDCHUNK, 0, ENDCHUNK.length);
128: cachePosition = 0;
129: }
130:
131: protected void writeClosingChunk() throws IOException {
132: // Write the final chunk.
133:
134: stream.write(ZERO, 0, ZERO.length);
135: stream.write(CRLF, 0, CRLF.length);
136: stream.write(ENDCHUNK, 0, ENDCHUNK.length);
137: }
138:
139: // ----------------------------------------------------------- Public Methods
140: /**
141: * Must be called to ensure the internal cache is flushed and the closing chunk is written.
142: * @throws IOException
143: *
144: * @since 3.0
145: */
146: public void finish() throws IOException {
147: if (!wroteLastChunk) {
148: flushCache();
149: writeClosingChunk();
150: wroteLastChunk = true;
151: }
152: }
153:
154: // -------------------------------------------- OutputStream Methods
155: /**
156: * Write the specified byte to our output stream.
157: *
158: * Note: Avoid this method as it will cause an inefficient single byte chunk.
159: * Use write (byte[], int, int) instead.
160: *
161: * @param b The byte to be written
162: * @throws IOException if an input/output error occurs
163: */
164: public void write(int b) throws IOException {
165: cache[cachePosition] = (byte) b;
166: cachePosition++;
167: if (cachePosition == cache.length)
168: flushCache();
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: * @param b
175: * @throws IOException
176: *
177: * @since 3.0
178: */
179: public void write(byte b[]) throws IOException {
180: this .write(b, 0, b.length);
181: }
182:
183: public void write(byte src[], int off, int len) throws IOException {
184: if (len >= cache.length - cachePosition) {
185: flushCacheWithAppend(src, off, len);
186: } else {
187: System.arraycopy(src, off, cache, cachePosition, len);
188: cachePosition += len;
189: }
190: }
191:
192: /**
193: * Flushes the underlying stream, but leaves the internal buffer alone.
194: * @throws IOException
195: */
196: public void flush() throws IOException {
197: stream.flush();
198: }
199:
200: /**
201: * Finishes writing to the underlying stream, but does NOT close the underlying stream.
202: * @throws IOException
203: */
204: public void close() throws IOException {
205: finish();
206: super.close();
207: }
208: }
|