Showing the changes to java/pkg/sun/net/www/http/ChunkedOutputStream.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,300 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2004, 2008, Oracle and/or its affiliates. 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. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | +package sun.net.www.http; | |
| 26 | + | |
| 27 | +import 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 | +public class ChunkedOutputStream extends PrintStream { | |
| 34 | + | |
| 35 | + /* Default chunk size (including chunk header) if not specified */ | |
| 36 | + static final int DEFAULT_CHUNK_SIZE = 4096; | |
| 37 | + private static final byte[] CRLF = {'\r', '\n'}; | |
| 38 | + private static final int CRLF_SIZE = CRLF.length; | |
| 39 | + private static final byte[] FOOTER = CRLF; | |
| 40 | + private static final int FOOTER_SIZE = CRLF_SIZE; | |
| 41 | + private static final byte[] EMPTY_CHUNK_HEADER = getHeader(0); | |
| 42 | + private static final int EMPTY_CHUNK_HEADER_SIZE = getHeaderSize(0); | |
| 43 | + | |
| 44 | + /* internal buffer */ | |
| 45 | + private byte buf[]; | |
| 46 | + /* size of data (excluding footers and headers) already stored in buf */ | |
| 47 | + private int size; | |
| 48 | + /* current index in buf (i.e. buf[count] */ | |
| 49 | + private int count; | |
| 50 | + /* number of bytes to be filled up to complete a data chunk | |
| 51 | + * currently being built */ | |
| 52 | + private int spaceInCurrentChunk; | |
| 53 | + | |
| 54 | + /* underlying stream */ | |
| 55 | + private PrintStream out; | |
| 56 | + | |
| 57 | + /* the chunk size we use */ | |
| 58 | + private int preferredChunkDataSize; | |
| 59 | + private int preferedHeaderSize; | |
| 60 | + private int preferredChunkGrossSize; | |
| 61 | + /* header for a complete Chunk */ | |
| 62 | + private byte[] completeHeader; | |
| 63 | + | |
| 64 | + /* return the size of the header for a particular chunk size */ | |
| 65 | + private static int getHeaderSize(int size) { | |
| 66 | + return (Integer.toHexString(size)).length() + CRLF_SIZE; | |
| 67 | + } | |
| 68 | + | |
| 69 | + /* return a header for a particular chunk size */ | |
| 70 | + private static byte[] getHeader(int size){ | |
| 71 | + try { | |
| 72 | + String hexStr = Integer.toHexString(size); | |
| 73 | + byte[] hexBytes = hexStr.getBytes("US-ASCII"); | |
| 74 | + byte[] header = new byte[getHeaderSize(size)]; | |
| 75 | + for (int i=0; i<hexBytes.length; i++) | |
| 76 | + header[i] = hexBytes[i]; | |
| 77 | + header[hexBytes.length] = CRLF[0]; | |
| 78 | + header[hexBytes.length+1] = CRLF[1]; | |
| 79 | + return header; | |
| 80 | + } catch (java.io.UnsupportedEncodingException e) { | |
| 81 | + /* This should never happen */ | |
| 82 | + throw new InternalError(e.getMessage()); | |
| 83 | + } | |
| 84 | + } | |
| 85 | + | |
| 86 | + public ChunkedOutputStream(PrintStream o) { | |
| 87 | + this(o, DEFAULT_CHUNK_SIZE); | |
| 88 | + } | |
| 89 | + | |
| 90 | + public ChunkedOutputStream(PrintStream o, int size) { | |
| 91 | + super(o); | |
| 92 | + out = o; | |
| 93 | + | |
| 94 | + if (size <= 0) { | |
| 95 | + size = DEFAULT_CHUNK_SIZE; | |
| 96 | + } | |
| 97 | + | |
| 98 | + /* Adjust the size to cater for the chunk header - eg: if the | |
| 99 | + * preferred chunk size is 1k this means the chunk size should | |
| 100 | + * be 1017 bytes (differs by 7 from preferred size because of | |
| 101 | + * 3 bytes for chunk size in hex and CRLF (header) and CRLF (footer)). | |
| 102 | + * | |
| 103 | + * If headerSize(adjusted_size) is shorter then headerSize(size) | |
| 104 | + * then try to use the extra byte unless headerSize(adjusted_size+1) | |
| 105 | + * increases back to headerSize(size) | |
| 106 | + */ | |
| 107 | + if (size > 0) { | |
| 108 | + int adjusted_size = size - getHeaderSize(size) - FOOTER_SIZE; | |
| 109 | + if (getHeaderSize(adjusted_size+1) < getHeaderSize(size)){ | |
| 110 | + adjusted_size++; | |
| 111 | + } | |
| 112 | + size = adjusted_size; | |
| 113 | + } | |
| 114 | + | |
| 115 | + if (size > 0) { | |
| 116 | + preferredChunkDataSize = size; | |
| 117 | + } else { | |
| 118 | + preferredChunkDataSize = DEFAULT_CHUNK_SIZE - | |
| 119 | + getHeaderSize(DEFAULT_CHUNK_SIZE) - FOOTER_SIZE; | |
| 120 | + } | |
| 121 | + | |
| 122 | + preferedHeaderSize = getHeaderSize(preferredChunkDataSize); | |
| 123 | + preferredChunkGrossSize = preferedHeaderSize + preferredChunkDataSize | |
| 124 | + + FOOTER_SIZE; | |
| 125 | + completeHeader = getHeader(preferredChunkDataSize); | |
| 126 | + | |
| 127 | + /* start with an initial buffer */ | |
| 128 | + buf = new byte[preferredChunkDataSize + 32]; | |
| 129 | + reset(); | |
| 130 | + } | |
| 131 | + | |
| 132 | + /* | |
| 133 | + * Flush a buffered, completed chunk to an underlying stream. If the data in | |
| 134 | + * the buffer is insufficient to build up a chunk of "preferredChunkSize" | |
| 135 | + * then the data do not get flushed unless flushAll is true. If flushAll is | |
| 136 | + * true then the remaining data builds up a last chunk which size is smaller | |
| 137 | + * than preferredChunkSize, and then the last chunk gets flushed to | |
| 138 | + * underlying stream. If flushAll is true and there is no data in a buffer | |
| 139 | + * at all then an empty chunk (containing a header only) gets flushed to | |
| 140 | + * underlying stream. | |
| 141 | + */ | |
| 142 | + private void flush(boolean flushAll) { | |
| 143 | + if (spaceInCurrentChunk == 0) { | |
| 144 | + /* flush a completed chunk to underlying stream */ | |
| 145 | + out.write(buf, 0, preferredChunkGrossSize); | |
| 146 | + out.flush(); | |
| 147 | + reset(); | |
| 148 | + } else if (flushAll){ | |
| 149 | + /* complete the last chunk and flush it to underlying stream */ | |
| 150 | + if (size > 0){ | |
| 151 | + /* adjust a header start index in case the header of the last | |
| 152 | + * chunk is shorter then preferedHeaderSize */ | |
| 153 | + | |
| 154 | + int adjustedHeaderStartIndex = preferedHeaderSize - | |
| 155 | + getHeaderSize(size); | |
| 156 | + | |
| 157 | + /* write header */ | |
| 158 | + System.arraycopy(getHeader(size), 0, buf, | |
| 159 | + adjustedHeaderStartIndex, getHeaderSize(size)); | |
| 160 | + | |
| 161 | + /* write footer */ | |
| 162 | + buf[count++] = FOOTER[0]; | |
| 163 | + buf[count++] = FOOTER[1]; | |
| 164 | + | |
| 165 | + //send the last chunk to underlying stream | |
| 166 | + out.write(buf, adjustedHeaderStartIndex, count - adjustedHeaderStartIndex); | |
| 167 | + } else { | |
| 168 | + //send an empty chunk (containing just a header) to underlying stream | |
| 169 | + out.write(EMPTY_CHUNK_HEADER, 0, EMPTY_CHUNK_HEADER_SIZE); | |
| 170 | + } | |
| 171 | + | |
| 172 | + out.flush(); | |
| 173 | + reset(); | |
| 174 | + } | |
| 175 | + } | |
| 176 | + | |
| 177 | + @Override | |
| 178 | + public boolean checkError() { | |
| 179 | + return out.checkError(); | |
| 180 | + } | |
| 181 | + | |
| 182 | + /* Check that the output stream is still open */ | |
| 183 | + private void ensureOpen() { | |
| 184 | + if (out == null) | |
| 185 | + setError(); | |
| 186 | + } | |
| 187 | + | |
| 188 | + /* | |
| 189 | + * Writes data from b[] to an internal buffer and stores the data as data | |
| 190 | + * chunks of a following format: {Data length in Hex}{CRLF}{data}{CRLF} | |
| 191 | + * The size of the data is preferredChunkSize. As soon as a completed chunk | |
| 192 | + * is read from b[] a process of reading from b[] suspends, the chunk gets | |
| 193 | + * flushed to the underlying stream and then the reading process from b[] | |
| 194 | + * continues. When there is no more sufficient data in b[] to build up a | |
| 195 | + * chunk of preferredChunkSize size the data get stored as an incomplete | |
| 196 | + * chunk of a following format: {space for data length}{CRLF}{data} | |
| 197 | + * The size of the data is of course smaller than preferredChunkSize. | |
| 198 | + */ | |
| 199 | + @Override | |
| 200 | + public synchronized void write(byte b[], int off, int len) { | |
| 201 | + ensureOpen(); | |
| 202 | + if ((off < 0) || (off > b.length) || (len < 0) || | |
| 203 | + ((off + len) > b.length) || ((off + len) < 0)) { | |
| 204 | + throw new IndexOutOfBoundsException(); | |
| 205 | + } else if (len == 0) { | |
| 206 | + return; | |
| 207 | + } | |
| 208 | + | |
| 209 | + /* if b[] contains enough data then one loop cycle creates one complete | |
| 210 | + * data chunk with a header, body and a footer, and then flushes the | |
| 211 | + * chunk to the underlying stream. Otherwise, the last loop cycle | |
| 212 | + * creates incomplete data chunk with empty header and with no footer | |
| 213 | + * and stores this incomplete chunk in an internal buffer buf[] | |
| 214 | + */ | |
| 215 | + int bytesToWrite = len; | |
| 216 | + int inputIndex = off; /* the index of the byte[] currently being written */ | |
| 217 | + | |
| 218 | + do { | |
| 219 | + /* enough data to complete a chunk */ | |
| 220 | + if (bytesToWrite >= spaceInCurrentChunk) { | |
| 221 | + | |
| 222 | + /* header */ | |
| 223 | + for (int i=0; i<completeHeader.length; i++) | |
| 224 | + buf[i] = completeHeader[i]; | |
| 225 | + | |
| 226 | + /* data */ | |
| 227 | + System.arraycopy(b, inputIndex, buf, count, spaceInCurrentChunk); | |
| 228 | + inputIndex += spaceInCurrentChunk; | |
| 229 | + bytesToWrite -= spaceInCurrentChunk; | |
| 230 | + count += spaceInCurrentChunk; | |
| 231 | + | |
| 232 | + /* footer */ | |
| 233 | + buf[count++] = FOOTER[0]; | |
| 234 | + buf[count++] = FOOTER[1]; | |
| 235 | + spaceInCurrentChunk = 0; //chunk is complete | |
| 236 | + | |
| 237 | + flush(false); | |
| 238 | + if (checkError()){ | |
| 239 | + break; | |
| 240 | + } | |
| 241 | + } | |
| 242 | + | |
| 243 | + /* not enough data to build a chunk */ | |
| 244 | + else { | |
| 245 | + /* header */ | |
| 246 | + /* do not write header if not enough bytes to build a chunk yet */ | |
| 247 | + | |
| 248 | + /* data */ | |
| 249 | + System.arraycopy(b, inputIndex, buf, count, bytesToWrite); | |
| 250 | + count += bytesToWrite; | |
| 251 | + size += bytesToWrite; | |
| 252 | + spaceInCurrentChunk -= bytesToWrite; | |
| 253 | + bytesToWrite = 0; | |
| 254 | + | |
| 255 | + /* footer */ | |
| 256 | + /* do not write header if not enough bytes to build a chunk yet */ | |
| 257 | + } | |
| 258 | + } while (bytesToWrite > 0); | |
| 259 | + } | |
| 260 | + | |
| 261 | + @Override | |
| 262 | + public synchronized void write(int _b) { | |
| 263 | + byte b[] = {(byte)_b}; | |
| 264 | + write(b, 0, 1); | |
| 265 | + } | |
| 266 | + | |
| 267 | + public synchronized void reset() { | |
| 268 | + count = preferedHeaderSize; | |
| 269 | + size = 0; | |
| 270 | + spaceInCurrentChunk = preferredChunkDataSize; | |
| 271 | + } | |
| 272 | + | |
| 273 | + public int size() { | |
| 274 | + return size; | |
| 275 | + } | |
| 276 | + | |
| 277 | + @Override | |
| 278 | + public synchronized void close() { | |
| 279 | + ensureOpen(); | |
| 280 | + | |
| 281 | + /* if we have buffer a chunked send it */ | |
| 282 | + if (size > 0) { | |
| 283 | + flush(true); | |
| 284 | + } | |
| 285 | + | |
| 286 | + /* send a zero length chunk */ | |
| 287 | + flush(true); | |
| 288 | + | |
| 289 | + /* don't close the underlying stream */ | |
| 290 | + out = null; | |
| 291 | + } | |
| 292 | + | |
| 293 | + @Override | |
| 294 | + public synchronized void flush() { | |
| 295 | + ensureOpen(); | |
| 296 | + if (size > 0) { | |
| 297 | + flush(true); | |
| 298 | + } | |
| 299 | + } | |
| 300 | +} | |