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); |