| Index: components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java
|
| diff --git a/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java b/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ecc4fb1aaac6d39fd8b968e7771f181ee85d5330
|
| --- /dev/null
|
| +++ b/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java
|
| @@ -0,0 +1,163 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.net.urlconnection;
|
| +
|
| +import org.chromium.net.UploadDataSink;
|
| +
|
| +import java.io.IOException;
|
| +import java.net.ProtocolException;
|
| +import java.nio.ByteBuffer;
|
| +
|
| +/**
|
| + * An implementation of {@link java.io.OutputStream} that buffers entire request
|
| + * body in memory. This is used when neither
|
| + * {@link CronetHttpURLConnection#setFixedLengthStreamingMode}
|
| + * nor {@link CronetHttpURLConnection#setChunkedStreamingMode} is set.
|
| + */
|
| +final class CronetBufferedOutputStream extends CronetOutputStream {
|
| + // QUIC uses a read buffer of 14520 bytes, SPDY uses 2852 bytes, and normal
|
| + // stream uses 16384 bytes. Therefore, use 16384 for now to avoid growing
|
| + // the buffer too many times.
|
| + private static final int INITIAL_BUFFER_SIZE = 16384;
|
| + // If content length is not passed in the constructor, this is -1.
|
| + private final int mInitialContentLength;
|
| + private final CronetHttpURLConnection mConnection;
|
| + // Internal buffer that is used to buffer the request body.
|
| + private ByteBuffer mBuffer;
|
| + private boolean mConnected = false;
|
| +
|
| + /**
|
| + * Package protected constructor.
|
| + * @param connection The CronetHttpURLConnection object.
|
| + * @param contentLength The content length of the request body. It must not
|
| + * be smaller than 0 or bigger than {@link Integer.MAX_VALUE}.
|
| + */
|
| + CronetBufferedOutputStream(final CronetHttpURLConnection connection,
|
| + final long contentLength) {
|
| + if (connection == null) {
|
| + throw new NullPointerException("Argument connection cannot be null.");
|
| + }
|
| +
|
| + if (contentLength > Integer.MAX_VALUE) {
|
| + throw new IllegalArgumentException("Use setFixedLengthStreamingMode()"
|
| + + " or setChunkedStreamingMode() for requests larger than 2GB.");
|
| + }
|
| + if (contentLength < 0) {
|
| + throw new IllegalArgumentException("Content length < 0.");
|
| + }
|
| + mConnection = connection;
|
| + mInitialContentLength = (int) contentLength;
|
| + mBuffer = ByteBuffer.allocate(mInitialContentLength);
|
| + }
|
| +
|
| + /**
|
| + * Package protected constructor used when content length is not known.
|
| + * @param connection The CronetHttpURLConnection object.
|
| + */
|
| + CronetBufferedOutputStream(final CronetHttpURLConnection connection) {
|
| + if (connection == null) {
|
| + throw new NullPointerException();
|
| + }
|
| +
|
| + mConnection = connection;
|
| + mInitialContentLength = -1;
|
| + // Buffering without knowing content-length.
|
| + mBuffer = ByteBuffer.allocate(INITIAL_BUFFER_SIZE);
|
| + }
|
| +
|
| + @Override
|
| + public void write(int oneByte) throws IOException {
|
| + ensureCanWrite(1);
|
| + mBuffer.put((byte) oneByte);
|
| + }
|
| +
|
| + @Override
|
| + public void write(byte[] buffer, int offset, int count) throws IOException {
|
| + ensureCanWrite(count);
|
| + mBuffer.put(buffer, offset, count);
|
| + }
|
| +
|
| + // TODO(xunjieli): implement close().
|
| +
|
| + /**
|
| + * Ensures that {@code count} bytes can be written to the internal buffer.
|
| + */
|
| + private void ensureCanWrite(int count) throws IOException {
|
| + if (mInitialContentLength != -1
|
| + && mBuffer.position() + count > mInitialContentLength) {
|
| + // Error message is to match that of the default implementation.
|
| + throw new ProtocolException("exceeded content-length limit of "
|
| + + mInitialContentLength + " bytes");
|
| + }
|
| + if (mConnected) {
|
| + throw new IllegalStateException("Cannot write after being connected.");
|
| + }
|
| + if (mInitialContentLength != -1) {
|
| + // If mInitialContentLength is known, the buffer should not grow.
|
| + return;
|
| + }
|
| + if (mBuffer.limit() - mBuffer.position() > count) {
|
| + // If there is enough capacity, the buffer should not grow.
|
| + return;
|
| + }
|
| + int afterSize = Math.max(mBuffer.capacity() * 2, mBuffer.capacity() + count);
|
| + ByteBuffer newByteBuffer = ByteBuffer.allocate(afterSize);
|
| + mBuffer.flip();
|
| + newByteBuffer.put(mBuffer);
|
| + mBuffer = newByteBuffer;
|
| + }
|
| +
|
| + // Below are CronetOutputStream implementations:
|
| +
|
| + /**
|
| + * Sets {@link #mConnected} to {@code true}.
|
| + */
|
| + @Override
|
| + void setConnected() throws IOException {
|
| + mConnected = true;
|
| + if (mBuffer.position() < mInitialContentLength) {
|
| + throw new ProtocolException("Content received is less than Content-Length");
|
| + }
|
| + // Flip the buffer to prepare it for UploadDataProvider read calls.
|
| + mBuffer.flip();
|
| + }
|
| +
|
| + @Override
|
| + void checkReceivedEnoughContent() throws IOException {
|
| + // Already checked in setConnected. Skip the check here, since mBuffer
|
| + // might be flipped.
|
| + }
|
| +
|
| + @Override
|
| + public long getLength() {
|
| + // This method is supposed to be called just before starting the request.
|
| + // If content length is not initially passed in, the number of bytes
|
| + // written will be used as the content length.
|
| + // TODO(xunjieli): Think of a less fragile way, since getLength() can be
|
| + // potentially called in other places in the future.
|
| + if (mInitialContentLength == -1) {
|
| + return mBuffer.position();
|
| + }
|
| + return mInitialContentLength;
|
| + }
|
| +
|
| + @Override
|
| + public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
|
| + int availableSpace = byteBuffer.capacity() - byteBuffer.position();
|
| + if (availableSpace < mBuffer.limit() - mBuffer.position()) {
|
| + byteBuffer.put(mBuffer.array(), mBuffer.position(), availableSpace);
|
| + mBuffer.position(mBuffer.position() + availableSpace);
|
| + } else {
|
| + byteBuffer.put(mBuffer);
|
| + }
|
| + uploadDataSink.onReadSucceeded(false);
|
| + }
|
| +
|
| + @Override
|
| + public void rewind(UploadDataSink uploadDataSink) {
|
| + mBuffer.position(0);
|
| + uploadDataSink.onRewindSucceeded();
|
| + }
|
| +}
|
|
|