blob: a06b5afe22867fc2f7c7f883a667014ec3b489e7 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2004-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package sun.net.www.http;
26
27import java.io.*;
28
29/**
30 * OutputStream that sends the output to the underlying stream using chunked
31 * encoding as specified in RFC 2068.
32 *
33 * @author Alan Bateman
34 */
35public class ChunkedOutputStream extends PrintStream {
36
37 /* Default chunk size (including chunk header) if not specified */
38 static final int DEFAULT_CHUNK_SIZE = 4096;
39
40 /* internal buffer */
41 private byte buf[];
42 private int count;
43
44 /* underlying stream */
45 private PrintStream out;
46
47 /* the chunk size we use */
48 private int preferredChunkSize;
49
50 /* if the users write buffer is bigger than this size, we
51 * write direct from the users buffer instead of copying
52 */
53 static final int MAX_BUF_SIZE = 10 * 1024;
54
55 /* return the size of the header for a particular chunk size */
56 private int headerSize(int size) {
57 return 2 + (Integer.toHexString(size)).length();
58 }
59
60 public ChunkedOutputStream(PrintStream o) {
61 this(o, DEFAULT_CHUNK_SIZE);
62 }
63
64 public ChunkedOutputStream(PrintStream o, int size) {
65 super(o);
66
67 out = o;
68
69 if (size <= 0) {
70 size = DEFAULT_CHUNK_SIZE;
71 }
72 /* Adjust the size to cater for the chunk header - eg: if the
73 * preferred chunk size is 1k this means the chunk size should
74 * be 1019 bytes (differs by 5 from preferred size because of
75 * 3 bytes for chunk size in hex and CRLF).
76 */
77 if (size > 0) {
78 int adjusted_size = size - headerSize(size);
79 if (adjusted_size + headerSize(adjusted_size) < size) {
80 adjusted_size++;
81 }
82 size = adjusted_size;
83 }
84
85 if (size > 0) {
86 preferredChunkSize = size;
87 } else {
88 preferredChunkSize = DEFAULT_CHUNK_SIZE - headerSize(DEFAULT_CHUNK_SIZE);
89 }
90
91 /* start with an initial buffer */
92 buf = new byte[preferredChunkSize + 32];
93 }
94
95 /*
96 * If flushAll is true, then all data is flushed in one chunk.
97 *
98 * If false and the size of the buffer data exceeds the preferred
99 * chunk size then chunks are flushed to the output stream.
100 * If there isn't enough data to make up a complete chunk,
101 * then the method returns.
102 */
103 private void flush(byte[] buf, boolean flushAll) {
104 flush (buf, flushAll, 0);
105 }
106
107 private void flush(byte[] buf, boolean flushAll, int offset) {
108 int chunkSize;
109
110 do {
111 if (count < preferredChunkSize) {
112 if (!flushAll) {
113 break;
114 }
115 chunkSize = count;
116 } else {
117 chunkSize = preferredChunkSize;
118 }
119
120 byte[] bytes = null;
121 try {
122 bytes = (Integer.toHexString(chunkSize)).getBytes("US-ASCII");
123 } catch (java.io.UnsupportedEncodingException e) {
124 //This should never happen.
125 throw new InternalError(e.getMessage());
126 }
127
128 out.write(bytes, 0, bytes.length);
129 out.write((byte)'\r');
130 out.write((byte)'\n');
131 if (chunkSize > 0) {
132 out.write(buf, offset, chunkSize);
133 out.write((byte)'\r');
134 out.write((byte)'\n');
135 }
136 out.flush();
137 if (checkError()) {
138 break;
139 }
140 if (chunkSize > 0) {
141 count -= chunkSize;
142 offset += chunkSize;
143 }
144 } while (count > 0);
145
146 if (!checkError() && count > 0) {
147 System.arraycopy(buf, offset, this.buf, 0, count);
148 }
149 }
150
151 public boolean checkError() {
152 return out.checkError();
153 }
154
155 /*
156 * Check if we have enough data for a chunk and if so flush to the
157 * underlying output stream.
158 */
159 private void checkFlush() {
160 if (count >= preferredChunkSize) {
161 flush(buf, false);
162 }
163 }
164
165 /* Check that the output stream is still open */
166 private void ensureOpen() {
167 if (out == null)
168 setError();
169 }
170
171 public synchronized void write(byte b[], int off, int len) {
172 ensureOpen();
173 if ((off < 0) || (off > b.length) || (len < 0) ||
174 ((off + len) > b.length) || ((off + len) < 0)) {
175 throw new IndexOutOfBoundsException();
176 } else if (len == 0) {
177 return;
178 }
179
180 if (len > MAX_BUF_SIZE) {
181 /* first finish the current chunk */
182 int l = preferredChunkSize - count;
183 if (l > 0) {
184 System.arraycopy(b, off, buf, count, l);
185 count = preferredChunkSize;
186 flush(buf, false);
187 }
188 count = len - l;
189 /* Now write the rest of the data */
190 flush (b, false, l+off);
191 } else {
192 int newcount = count + len;
193
194 if (newcount > buf.length) {
195 byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
196 System.arraycopy(buf, 0, newbuf, 0, count);
197 buf = newbuf;
198 }
199 System.arraycopy(b, off, buf, count, len);
200 count = newcount;
201 checkFlush();
202 }
203 }
204
205 public synchronized void write(int b) {
206 ensureOpen();
207 int newcount = count + 1;
208 if (newcount > buf.length) {
209 byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
210 System.arraycopy(buf, 0, newbuf, 0, count);
211 buf = newbuf;
212 }
213 buf[count] = (byte)b;
214 count = newcount;
215 checkFlush();
216 }
217
218 public synchronized void reset() {
219 count = 0;
220 }
221
222 public int size() {
223 return count;
224 }
225
226 public synchronized void close() {
227 ensureOpen();
228
229 /* if we have buffer a chunked send it */
230 if (count > 0) {
231 flush(buf, true);
232 }
233
234 /* send a zero length chunk */
235 flush(buf, true);
236
237 /* don't close the underlying stream */
238 out = null;
239 }
240
241 public synchronized void flush() {
242 ensureOpen();
243 if (count > 0) {
244 flush(buf, true);
245 }
246 }
247
248}