| Index: components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
|
| diff --git a/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java b/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
|
| index 91268c3f072eca2d7e7b89799d18215a4d76dd3b..ae22787434fd208d2e991a069ec1d5bad61e1204 100644
|
| --- a/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
|
| +++ b/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
|
| @@ -22,6 +22,7 @@ import java.util.HashMap;
|
| import java.util.List;
|
| import java.util.Map;
|
| import java.util.Map.Entry;
|
| +import java.util.concurrent.Semaphore;
|
|
|
| /**
|
| * Network request using the native http stack implementation.
|
| @@ -41,6 +42,7 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| private String mUploadContentType;
|
| private String mMethod;
|
| private byte[] mUploadData;
|
| + private Semaphore mAppendChunkSemaphore;
|
| private ReadableByteChannel mUploadChannel;
|
| private WritableByteChannel mOutputChannel;
|
| private IOException mSinkException;
|
| @@ -220,6 +222,7 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| mUploadContentType = contentType;
|
| mUploadData = data;
|
| mUploadChannel = null;
|
| + mAppendChunkSemaphore = null;
|
| }
|
| }
|
|
|
| @@ -240,9 +243,28 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| mUploadChannel = channel;
|
| mUploadContentLength = contentLength;
|
| mUploadData = null;
|
| + mAppendChunkSemaphore = null;
|
| }
|
| }
|
|
|
| + /**
|
| + * Creates writable byte channel for chunked uploads as part of a POST or
|
| + * PUT request.
|
| + *
|
| + * @param contentType MIME type of the upload content.
|
| + */
|
| + public WritableByteChannel createChunkedUploadChannel(String contentType) {
|
| + synchronized (mLock) {
|
| + validateNotStarted();
|
| + mUploadContentType = contentType;
|
| + mUploadChannel = null;
|
| + mUploadContentLength = 0;
|
| + mUploadData = null;
|
| + mAppendChunkSemaphore = new Semaphore(0);
|
| + }
|
| + return new UploadChannel();
|
| + }
|
| +
|
| /**
|
| * Sets HTTP method for upload request. Only PUT or POST are allowed.
|
| */
|
| @@ -303,6 +325,9 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| } else if (mUploadChannel != null) {
|
| nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType,
|
| mUploadContentLength);
|
| + } else if (mUploadContentType != null) {
|
| + nativeEnableChunkedUpload(mUrlRequestAdapter,
|
| + mUploadContentType);
|
| }
|
|
|
| nativeStart(mUrlRequestAdapter);
|
| @@ -378,6 +403,40 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| cancel();
|
| }
|
|
|
| + /**
|
| + * Invokes {@link #nativeAppendChunk(long, ByteBuffer, int, boolean)} and
|
| + * waits for it to notify completion.
|
| + * @param chunk The data. It's position must be zero.
|
| + * @param isLastChunk Whether chunk is the last one.
|
| + */
|
| + void appendChunkBlocking(ByteBuffer chunk, boolean isLastChunk)
|
| + throws IOException {
|
| + if (chunk.position() != 0) {
|
| + throw new IllegalArgumentException("The position must be zero.");
|
| + }
|
| + synchronized (mLock) {
|
| + if (mUrlRequestAdapter == 0) {
|
| + throw new IOException("Native adapter is destroyed.");
|
| + }
|
| + nativeAppendChunkToUpload(mUrlRequestAdapter, chunk, chunk.limit(),
|
| + false);
|
| + // Wait for the data to be actually consumed.
|
| + try {
|
| + mAppendChunkSemaphore.acquire();
|
| + } catch (InterruptedException e) {
|
| + // We were interrupted before the data was uploaded. Recovering
|
| + // from this state is complicated so we cancel the upload
|
| + // operation and fail.
|
| + Thread.currentThread().interrupt();
|
| +
|
| + // TODO(miloslav): Not sure why do we set mSinkException here.
|
| + mSinkException = new IOException("Upload interrupted", e);
|
| + cancel();
|
| + throw mSinkException;
|
| + }
|
| + }
|
| + }
|
| +
|
| /**
|
| * A callback invoked when the response has been fully consumed.
|
| */
|
| @@ -403,6 +462,68 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| }
|
| }
|
|
|
| + class UploadChannel implements WritableByteChannel {
|
| + private static final int UPLOAD_BYTE_BUFFER_SIZE = 32768;
|
| +
|
| + private boolean mOpen = true;
|
| + // Native wants a direct buffer.
|
| + private final ByteBuffer mBuffer =
|
| + ByteBuffer.allocateDirect(UPLOAD_BYTE_BUFFER_SIZE);
|
| +
|
| + @Override
|
| + public synchronized boolean isOpen() {
|
| + return mOpen;
|
| + }
|
| +
|
| + @Override
|
| + public synchronized void close() throws IOException {
|
| + Log.d(ChromiumUrlRequestContext.LOG_TAG,
|
| + "UploadChannel.close() url=" + getUrl());
|
| + if (!mOpen) {
|
| + return;
|
| + }
|
| +
|
| + mOpen = false;
|
| + mBuffer.clear();
|
| +
|
| + // NOOP If the native peer has been destroyed.
|
| + try {
|
| + Log.d(ChromiumUrlRequestContext.LOG_TAG,
|
| + "UploadChannel.close(): final chunk.");
|
| + appendChunkBlocking(mBuffer, true);
|
| + } catch (IOException e) {
|
| + Log.w(ChromiumUrlRequestContext.LOG_TAG,
|
| + "Ignoring exception during closing.", e);
|
| + }
|
| + Log.d(ChromiumUrlRequestContext.LOG_TAG,
|
| + "UploadChannel.close() done.");
|
| + }
|
| +
|
| + @Override
|
| + public synchronized int write(ByteBuffer sourceBuffer)
|
| + throws IOException {
|
| + Log.d(ChromiumUrlRequestContext.LOG_TAG, "UploadChannel.write(" +
|
| + sourceBuffer.remaining() + " bytes) url=" + getUrl());
|
| + int written = 0;
|
| + while (sourceBuffer.hasRemaining()) {
|
| + mBuffer.clear();
|
| + int oldLimit = sourceBuffer.limit();
|
| + if (sourceBuffer.remaining() > mBuffer.remaining()) {
|
| + sourceBuffer.limit(sourceBuffer.position()
|
| + + mBuffer.remaining());
|
| + }
|
| + mBuffer.put(sourceBuffer);
|
| + mBuffer.flip();
|
| + written += mBuffer.limit();
|
| + appendChunkBlocking(mBuffer, false);
|
| + sourceBuffer.limit(oldLimit);
|
| + }
|
| + Log.d(ChromiumUrlRequestContext.LOG_TAG,
|
| + "UploadChannel.write() returning");
|
| + return written;
|
| + }
|
| + }
|
| +
|
| // Private methods called by native library.
|
|
|
| /**
|
| @@ -423,6 +544,14 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| }
|
|
|
| /**
|
| + * A callback invoked when appending a chunk to the request has completed.
|
| + */
|
| + @CalledByNative
|
| + protected void onAppendChunkCompleted() {
|
| + mAppendChunkSemaphore.release();
|
| + }
|
| +
|
| + /**
|
| * A callback invoked when the first chunk of the response has arrived.
|
| */
|
| @CalledByNative
|
| @@ -605,6 +734,12 @@ public class ChromiumUrlRequest implements HttpUrlRequest {
|
| private native void nativeSetUploadChannel(long urlRequestAdapter,
|
| String contentType, long contentLength);
|
|
|
| + private native void nativeEnableChunkedUpload(long urlRequestAdapter,
|
| + String contentType);
|
| +
|
| + private native void nativeAppendChunkToUpload(long urlRequestAdapter,
|
| + ByteBuffer chunk, int chunkSize, boolean isLastChunk);
|
| +
|
| private native void nativeStart(long urlRequestAdapter);
|
|
|
| private native void nativeCancel(long urlRequestAdapter);
|
|
|