| 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..878d21b2455916b50857fa1fcedd906ecb4a153a
|
| --- /dev/null
|
| +++ b/components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java
|
| @@ -0,0 +1,153 @@
|
| +// 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.UploadDataProvider;
|
| +import org.chromium.net.UploadDataSink;
|
| +
|
| +import java.io.IOException;
|
| +import java.io.OutputStream;
|
| +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 OutputStream
|
| + implements UploadDataProvider {
|
| + // 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;
|
| + private boolean mFirstReadInitiated = 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();
|
| + }
|
| +
|
| + if (contentLength > Integer.MAX_VALUE) {
|
| + throw new IllegalStateException("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(2048);
|
| + }
|
| +
|
| + @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);
|
| + }
|
| +
|
| + /**
|
| + * Sets {@link #mConnected} to {@code true}.
|
| + */
|
| + 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();
|
| + }
|
| +
|
| + // 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 || mBuffer.limit() - mBuffer.position() > count) {
|
| + // If mInitialContentLength is known or 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 UploadDataProvider implementations. Only intended to be used
|
| + // within Cronet.
|
| +
|
| + @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.
|
| + if (mInitialContentLength == -1) {
|
| + return mBuffer.position();
|
| + }
|
| + return mInitialContentLength;
|
| + }
|
| +
|
| + @Override
|
| + public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
|
| + int oldPos = byteBuffer.position();
|
| + 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();
|
| + }
|
| +}
|
|
|