OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.net.urlconnection; |
| 6 |
| 7 import org.chromium.net.UploadDataSink; |
| 8 |
| 9 import java.io.IOException; |
| 10 import java.net.ProtocolException; |
| 11 import java.nio.ByteBuffer; |
| 12 |
| 13 /** |
| 14 * An implementation of {@link java.io.OutputStream} that buffers entire request |
| 15 * body in memory. This is used when neither |
| 16 * {@link CronetHttpURLConnection#setFixedLengthStreamingMode} |
| 17 * nor {@link CronetHttpURLConnection#setChunkedStreamingMode} is set. |
| 18 */ |
| 19 final class CronetBufferedOutputStream extends CronetOutputStream { |
| 20 // QUIC uses a read buffer of 14520 bytes, SPDY uses 2852 bytes, and normal |
| 21 // stream uses 16384 bytes. Therefore, use 16384 for now to avoid growing |
| 22 // the buffer too many times. |
| 23 private static final int INITIAL_BUFFER_SIZE = 16384; |
| 24 // If content length is not passed in the constructor, this is -1. |
| 25 private final int mInitialContentLength; |
| 26 private final CronetHttpURLConnection mConnection; |
| 27 // Internal buffer that is used to buffer the request body. |
| 28 private ByteBuffer mBuffer; |
| 29 private boolean mConnected = false; |
| 30 |
| 31 /** |
| 32 * Package protected constructor. |
| 33 * @param connection The CronetHttpURLConnection object. |
| 34 * @param contentLength The content length of the request body. It must not |
| 35 * be smaller than 0 or bigger than {@link Integer.MAX_VALUE}. |
| 36 */ |
| 37 CronetBufferedOutputStream(final CronetHttpURLConnection connection, |
| 38 final long contentLength) { |
| 39 if (connection == null) { |
| 40 throw new NullPointerException("Argument connection cannot be null."
); |
| 41 } |
| 42 |
| 43 if (contentLength > Integer.MAX_VALUE) { |
| 44 throw new IllegalArgumentException("Use setFixedLengthStreamingMode(
)" |
| 45 + " or setChunkedStreamingMode() for requests larger than 2GB.")
; |
| 46 } |
| 47 if (contentLength < 0) { |
| 48 throw new IllegalArgumentException("Content length < 0."); |
| 49 } |
| 50 mConnection = connection; |
| 51 mInitialContentLength = (int) contentLength; |
| 52 mBuffer = ByteBuffer.allocate(mInitialContentLength); |
| 53 } |
| 54 |
| 55 /** |
| 56 * Package protected constructor used when content length is not known. |
| 57 * @param connection The CronetHttpURLConnection object. |
| 58 */ |
| 59 CronetBufferedOutputStream(final CronetHttpURLConnection connection) { |
| 60 if (connection == null) { |
| 61 throw new NullPointerException(); |
| 62 } |
| 63 |
| 64 mConnection = connection; |
| 65 mInitialContentLength = -1; |
| 66 // Buffering without knowing content-length. |
| 67 mBuffer = ByteBuffer.allocate(INITIAL_BUFFER_SIZE); |
| 68 } |
| 69 |
| 70 @Override |
| 71 public void write(int oneByte) throws IOException { |
| 72 ensureCanWrite(1); |
| 73 mBuffer.put((byte) oneByte); |
| 74 } |
| 75 |
| 76 @Override |
| 77 public void write(byte[] buffer, int offset, int count) throws IOException { |
| 78 ensureCanWrite(count); |
| 79 mBuffer.put(buffer, offset, count); |
| 80 } |
| 81 |
| 82 // TODO(xunjieli): implement close(). |
| 83 |
| 84 /** |
| 85 * Ensures that {@code count} bytes can be written to the internal buffer. |
| 86 */ |
| 87 private void ensureCanWrite(int count) throws IOException { |
| 88 if (mInitialContentLength != -1 |
| 89 && mBuffer.position() + count > mInitialContentLength) { |
| 90 // Error message is to match that of the default implementation. |
| 91 throw new ProtocolException("exceeded content-length limit of " |
| 92 + mInitialContentLength + " bytes"); |
| 93 } |
| 94 if (mConnected) { |
| 95 throw new IllegalStateException("Cannot write after being connected.
"); |
| 96 } |
| 97 if (mInitialContentLength != -1) { |
| 98 // If mInitialContentLength is known, the buffer should not grow. |
| 99 return; |
| 100 } |
| 101 if (mBuffer.limit() - mBuffer.position() > count) { |
| 102 // If there is enough capacity, the buffer should not grow. |
| 103 return; |
| 104 } |
| 105 int afterSize = Math.max(mBuffer.capacity() * 2, mBuffer.capacity() + co
unt); |
| 106 ByteBuffer newByteBuffer = ByteBuffer.allocate(afterSize); |
| 107 mBuffer.flip(); |
| 108 newByteBuffer.put(mBuffer); |
| 109 mBuffer = newByteBuffer; |
| 110 } |
| 111 |
| 112 // Below are CronetOutputStream implementations: |
| 113 |
| 114 /** |
| 115 * Sets {@link #mConnected} to {@code true}. |
| 116 */ |
| 117 @Override |
| 118 void setConnected() throws IOException { |
| 119 mConnected = true; |
| 120 if (mBuffer.position() < mInitialContentLength) { |
| 121 throw new ProtocolException("Content received is less than Content-L
ength"); |
| 122 } |
| 123 // Flip the buffer to prepare it for UploadDataProvider read calls. |
| 124 mBuffer.flip(); |
| 125 } |
| 126 |
| 127 @Override |
| 128 void checkReceivedEnoughContent() throws IOException { |
| 129 // Already checked in setConnected. Skip the check here, since mBuffer |
| 130 // might be flipped. |
| 131 } |
| 132 |
| 133 @Override |
| 134 public long getLength() { |
| 135 // This method is supposed to be called just before starting the request
. |
| 136 // If content length is not initially passed in, the number of bytes |
| 137 // written will be used as the content length. |
| 138 // TODO(xunjieli): Think of a less fragile way, since getLength() can be |
| 139 // potentially called in other places in the future. |
| 140 if (mInitialContentLength == -1) { |
| 141 return mBuffer.position(); |
| 142 } |
| 143 return mInitialContentLength; |
| 144 } |
| 145 |
| 146 @Override |
| 147 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) { |
| 148 int availableSpace = byteBuffer.capacity() - byteBuffer.position(); |
| 149 if (availableSpace < mBuffer.limit() - mBuffer.position()) { |
| 150 byteBuffer.put(mBuffer.array(), mBuffer.position(), availableSpace); |
| 151 mBuffer.position(mBuffer.position() + availableSpace); |
| 152 } else { |
| 153 byteBuffer.put(mBuffer); |
| 154 } |
| 155 uploadDataSink.onReadSucceeded(false); |
| 156 } |
| 157 |
| 158 @Override |
| 159 public void rewind(UploadDataSink uploadDataSink) { |
| 160 mBuffer.position(0); |
| 161 uploadDataSink.onRewindSucceeded(); |
| 162 } |
| 163 } |
OLD | NEW |