1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.apache.commons.httpclient;
33
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39 /***
40 * <p>
41 * Wrapper supporting the chunked transfer encoding.
42 * </p>
43 *
44 * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
45 * @author Sean C. Sullivan
46 * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
47 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
48 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
49 * @version $Revision: 1.10.2.1 $ $Date: 2004/02/22 18:21:13 $
50 *
51 * @see ChunkedInputStream
52 * @since 2.0
53 *
54 */
55 public class ChunkedOutputStream extends OutputStream {
56
57
58
59 /*** <tt>"\r\n"</tt>, as bytes. */
60 private static final byte CRLF[] = new byte[] {(byte) 13, (byte) 10};
61
62 /*** End chunk */
63 private static final byte ENDCHUNK[] = CRLF;
64
65 /*** 0 */
66 private static final byte ZERO[] = new byte[] {(byte) '0'};
67
68 /*** 1 */
69 private static final byte ONE[] = new byte[] {(byte) '1'};
70
71 /*** Log object for this class. */
72 private static final Log LOG = LogFactory.getLog(ChunkedOutputStream.class);
73
74
75
76 /*** Has this stream been closed? */
77 private boolean closed = false;
78
79 /*** The underlying output stream to which we will write data */
80 private OutputStream stream = null;
81
82
83
84 /***
85 * Construct an output stream wrapping the given stream.
86 * The stream will not use chunking.
87 *
88 * @param stream wrapped output stream. Must be non-null.
89 */
90 public ChunkedOutputStream(OutputStream stream) {
91 if (stream == null) {
92 throw new NullPointerException("stream parameter is null");
93 }
94 this.stream = stream;
95 }
96
97
98
99
100 /***
101 * Writes a <code>String</code> to the client, without a carriage return
102 * line feed (CRLF) character at the end. The platform default encoding is
103 * used!
104 *
105 * @param s the <code>String</code> to send to the client. Must be non-null.
106 * @throws IOException if an input or output exception occurred
107 */
108 public void print(String s) throws IOException {
109 LOG.trace("enter ChunckedOutputStream.print(String)");
110 if (s == null) {
111 s = "null";
112 }
113 write(HttpConstants.getBytes(s));
114 }
115
116 /***
117 * Writes a carriage return-line feed (CRLF) to the client.
118 *
119 * @throws IOException if an input or output exception occurred
120 */
121 public void println() throws IOException {
122 print("\r\n");
123 }
124
125 /***
126 * Writes a <code>String</code> to the client,
127 * followed by a carriage return-line feed (CRLF).
128 *
129 * @param s the </code>String</code> to write to the client
130 * @exception IOException if an input or output exception occurred
131 */
132 public void println(String s) throws IOException {
133 print(s);
134 println();
135 }
136
137
138
139 /***
140 * Write the specified byte to our output stream.
141 *
142 * @param b The byte to be written
143 * @throws IOException if an input/output error occurs
144 * @throws IllegalStateException if stream already closed
145 */
146 public void write (int b) throws IOException, IllegalStateException {
147 if (closed) {
148 throw new IllegalStateException("Output stream already closed");
149 }
150
151 stream.write(ONE, 0, ONE.length);
152 stream.write(CRLF, 0, CRLF.length);
153 stream.write(b);
154 stream.write(ENDCHUNK, 0, ENDCHUNK.length);
155 LOG.debug("Writing chunk (length: 1)");
156 }
157
158 /***
159 * Write the specified byte array.
160 *
161 * @param b the byte array to write out
162 * @param off the offset within <code>b</code> to start writing from
163 * @param len the length of data within <code>b</code> to write
164 * @throws IOException when errors occur writing output
165 */
166 public void write (byte[] b, int off, int len) throws IOException {
167 LOG.trace("enter ChunckedOutputStream.write(byte[], int, int)");
168
169 if (closed) {
170 throw new IllegalStateException("Output stream already closed");
171 }
172 byte chunkHeader[] = HttpConstants.getBytes (
173 Integer.toHexString(len) + "\r\n");
174 stream.write(chunkHeader, 0, chunkHeader.length);
175 stream.write(b, off, len);
176 stream.write(ENDCHUNK, 0, ENDCHUNK.length);
177 if (LOG.isDebugEnabled()) {
178 LOG.debug("Writing chunk (length: " + len + ")");
179 }
180 }
181
182 /***
183 * Close this output stream, causing any buffered data to be flushed and
184 * any further output data to throw an IOException. The underlying stream
185 * is not closed!
186 *
187 * @throws IOException if an error occurs closing the stream
188 */
189 public void writeClosingChunk() throws IOException {
190 LOG.trace("enter ChunkedOutputStream.writeClosingChunk()");
191
192 if (!closed) {
193 try {
194
195 stream.write(ZERO, 0, ZERO.length);
196 stream.write(CRLF, 0, CRLF.length);
197 stream.write(ENDCHUNK, 0, ENDCHUNK.length);
198 LOG.debug("Writing closing chunk");
199 } catch (IOException e) {
200 LOG.debug("Unexpected exception caught when closing "
201 + "output stream", e);
202 throw e;
203 } finally {
204
205
206
207 closed = true;
208 }
209 }
210 }
211
212 /***
213 * Flushes the underlying stream.
214 * @throws IOException If an IO problem occurs.
215 */
216 public void flush() throws IOException {
217 stream.flush();
218 }
219
220 /***
221 * Close this output stream, causing any buffered data to be flushed and
222 * any further output data to throw an IOException. The underlying stream
223 * is not closed!
224 *
225 * @throws IOException if an error occurs closing the stream
226 */
227 public void close() throws IOException {
228 LOG.trace("enter ChunkedOutputStream.close()");
229 writeClosingChunk();
230 super.close();
231 }
232
233
234 }