| Index: components/cronet/android/java/src/org/chromium/net/UrlRequest.java
|
| diff --git a/components/cronet/android/java/src/org/chromium/net/UrlRequest.java b/components/cronet/android/java/src/org/chromium/net/UrlRequest.java
|
| index 73e9abac4030dd20e16712b8e4dad5d8ea893d22..13cb80306e2236259f297de8e2eb200952f4547f 100644
|
| --- a/components/cronet/android/java/src/org/chromium/net/UrlRequest.java
|
| +++ b/components/cronet/android/java/src/org/chromium/net/UrlRequest.java
|
| @@ -1,13 +1,15 @@
|
| // Copyright 2014 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;
|
|
|
| +import android.os.ConditionVariable;
|
| +
|
| import org.apache.http.conn.ConnectTimeoutException;
|
| import org.chromium.base.CalledByNative;
|
| import org.chromium.base.JNINamespace;
|
|
|
| import java.io.IOException;
|
| import java.net.MalformedURLException;
|
| import java.net.URL;
|
| @@ -35,24 +37,24 @@ public class UrlRequest {
|
| private final Map<String, String> mHeaders;
|
| private final WritableByteChannel mSink;
|
| private Map<String, String> mAdditionalHeaders;
|
| private String mUploadContentType;
|
| private String mMethod;
|
| private byte[] mUploadData;
|
| private ReadableByteChannel mUploadChannel;
|
| - private WritableByteChannel mOutputChannel;
|
| private IOException mSinkException;
|
| private volatile boolean mStarted;
|
| private volatile boolean mCanceled;
|
| private volatile boolean mRecycled;
|
| private volatile boolean mFinished;
|
| private boolean mHeadersAvailable;
|
| private String mContentType;
|
| private long mContentLength;
|
| private long mUploadContentLength;
|
| + private ConditionVariable mAppendChunkCondition;
|
| private final ContextLock mLock;
|
|
|
| /**
|
| * Native adapter object, owned by UrlRequest.
|
| */
|
| private long mUrlRequestAdapter;
|
|
|
| @@ -106,14 +108,15 @@ public class UrlRequest {
|
| */
|
| public void setUploadData(String contentType, byte[] data) {
|
| synchronized (mLock) {
|
| validateNotStarted();
|
| mUploadContentType = contentType;
|
| mUploadData = data;
|
| mUploadChannel = null;
|
| + mAppendChunkCondition = null;
|
| }
|
| }
|
|
|
| /**
|
| * Sets a readable byte channel to upload as part of a POST request.
|
| *
|
| * @param contentType MIME type of the post content or null if this is not a
|
| @@ -126,17 +129,72 @@ public class UrlRequest {
|
| ReadableByteChannel channel, long contentLength) {
|
| synchronized (mLock) {
|
| validateNotStarted();
|
| mUploadContentType = contentType;
|
| mUploadChannel = channel;
|
| mUploadContentLength = contentLength;
|
| mUploadData = null;
|
| + mAppendChunkCondition = null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Sets this request up for chunked uploading. To upload data call
|
| + * {@link #appendChunkBlocking(ByteBuffer, boolean)} after {@link #start()}.
|
| + *
|
| + * @param contentType MIME type of the post content or null if this is not a
|
| + * POST request.
|
| + */
|
| + public void setChunkedUpload(String contentType) {
|
| + synchronized (mLock) {
|
| + validateNotStarted();
|
| + mUploadContentType = contentType;
|
| + mAppendChunkCondition = new ConditionVariable();
|
| + mUploadData = null;
|
| + mUploadChannel = null;
|
| }
|
| }
|
|
|
| + /**
|
| + * Uploads a new chunk. Must have called {@link #setChunkedUpload(String)}
|
| + * and {@link #start()}.
|
| + *
|
| + * @param chunk The data, which will not be modified. It must not be empty
|
| + * and its current position must be zero.
|
| + * @param isLastChunk Whether this chunk is the last one.
|
| + */
|
| + public void appendChunkBlocking(ByteBuffer chunk, boolean isLastChunk)
|
| + throws IOException {
|
| + if (!chunk.hasRemaining()) {
|
| + throw new IllegalArgumentException(
|
| + "Attempted to write empty buffer.");
|
| + }
|
| + if (chunk.position() != 0) {
|
| + throw new IllegalArgumentException("The position must be zero.");
|
| + }
|
| + synchronized (mLock) {
|
| + if (!mStarted) {
|
| + throw new IllegalStateException("Request not yet started.");
|
| + }
|
| + if (mAppendChunkCondition == null) {
|
| + throw new IllegalStateException(
|
| + "Request not set for chunked uploadind.");
|
| + }
|
| + if (mUrlRequestAdapter == 0) {
|
| + throw new IOException("Native peer destroyed.");
|
| + }
|
| + mAppendChunkCondition.close();
|
| + nativeAppendChunk(mUrlRequestAdapter, chunk, chunk.limit(),
|
| + isLastChunk);
|
| + }
|
| + // Wait for the data to be actually consumed. Outside mLock to avoid
|
| + // deadlock.
|
| + mAppendChunkCondition.block();
|
| + }
|
| +
|
| public void setHttpMethod(String method) {
|
| validateNotStarted();
|
| if (!("PUT".equals(method) || "POST".equals(method))) {
|
| throw new IllegalArgumentException("Only PUT or POST are allowed.");
|
| }
|
| mMethod = method;
|
| }
|
| @@ -155,15 +213,15 @@ public class UrlRequest {
|
| validateNotRecycled();
|
|
|
| mStarted = true;
|
|
|
| String method = mMethod;
|
| if (method == null &&
|
| ((mUploadData != null && mUploadData.length > 0) ||
|
| - mUploadChannel != null)) {
|
| + mUploadChannel != null || mAppendChunkCondition != null)) {
|
| // Default to POST if there is data to upload but no method was
|
| // specified.
|
| method = "POST";
|
| }
|
|
|
| if (method != null) {
|
| nativeSetMethod(mUrlRequestAdapter, method);
|
| @@ -186,14 +244,17 @@ public class UrlRequest {
|
|
|
| if (mUploadData != null && mUploadData.length > 0) {
|
| nativeSetUploadData(mUrlRequestAdapter, mUploadContentType,
|
| mUploadData);
|
| } else if (mUploadChannel != null) {
|
| nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType,
|
| mUploadContentLength);
|
| + } else if (mAppendChunkCondition != null) {
|
| + nativeEnableChunkedUpload(mUrlRequestAdapter,
|
| + mUploadContentType);
|
| }
|
|
|
| nativeStart(mUrlRequestAdapter);
|
| }
|
| }
|
|
|
| public void cancel() {
|
| @@ -284,14 +345,22 @@ public class UrlRequest {
|
| validateHeadersAvailable();
|
| ResponseHeadersMap result = new ResponseHeadersMap();
|
| nativeGetAllHeaders(mUrlRequestAdapter, result);
|
| return result;
|
| }
|
|
|
| /**
|
| + * A callback invoked when appending a chunk to the request has completed.
|
| + */
|
| + @CalledByNative
|
| + protected void onAppendChunkCompleted() {
|
| + mAppendChunkCondition.open();
|
| + }
|
| +
|
| + /**
|
| * A callback invoked when the first chunk of the response has arrived.
|
| */
|
| @CalledByNative
|
| protected void onResponseStarted() {
|
| mContentType = nativeGetContentType(mUrlRequestAdapter);
|
| mContentLength = nativeGetContentLength(mUrlRequestAdapter);
|
| mHeadersAvailable = true;
|
| @@ -326,14 +395,17 @@ public class UrlRequest {
|
| * Notifies the listener, releases native data structures.
|
| */
|
| @SuppressWarnings("unused")
|
| @CalledByNative
|
| private void finish() {
|
| synchronized (mLock) {
|
| mFinished = true;
|
| + if (mAppendChunkCondition != null) {
|
| + mAppendChunkCondition.open();
|
| + }
|
|
|
| if (mRecycled) {
|
| return;
|
| }
|
| try {
|
| mSink.close();
|
| } catch (IOException e) {
|
| @@ -421,14 +493,20 @@ public class UrlRequest {
|
|
|
| private native void nativeSetUploadData(long urlRequestAdapter,
|
| String contentType, byte[] content);
|
|
|
| private native void nativeSetUploadChannel(long urlRequestAdapter,
|
| String contentType, long contentLength);
|
|
|
| + private native void nativeEnableChunkedUpload(long urlRequestAdapter,
|
| + String contentType);
|
| +
|
| + private native void nativeAppendChunk(long urlRequestAdapter,
|
| + ByteBuffer chunk, int chunkSize, boolean isLastChunk);
|
| +
|
| private native void nativeStart(long urlRequestAdapter);
|
|
|
| private native void nativeCancel(long urlRequestAdapter);
|
|
|
| private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
|
|
|
| private native int nativeGetErrorCode(long urlRequestAdapter);
|
|
|