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 | } | ||