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..f8912a39c30cb2db4b83f496e17654a7299d8b7e 100644 |
--- a/components/cronet/android/java/src/org/chromium/net/UrlRequest.java |
+++ b/components/cronet/android/java/src/org/chromium/net/UrlRequest.java |
@@ -16,20 +16,22 @@ import java.nio.ByteBuffer; |
import java.nio.channels.ReadableByteChannel; |
import java.nio.channels.WritableByteChannel; |
import java.util.ArrayList; |
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. |
*/ |
@JNINamespace("cronet") |
public class UrlRequest { |
+ |
mef
2014/08/15 13:30:22
nit: not needed nl?
mdumitrescu
2014/08/15 15:23:12
Done.
|
private static final class ContextLock { |
} |
private final UrlRequestContext mRequestContext; |
private final String mUrl; |
private final int mPriority; |
private final Map<String, String> mHeaders; |
@@ -45,14 +47,15 @@ public class UrlRequest { |
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 Semaphore mAppendChunkSemaphore; |
private final ContextLock mLock; |
/** |
* Native adapter object, owned by UrlRequest. |
*/ |
private long mUrlRequestAdapter; |
@@ -129,14 +132,64 @@ public class UrlRequest { |
mUploadContentType = contentType; |
mUploadChannel = channel; |
mUploadContentLength = contentLength; |
mUploadData = null; |
} |
} |
+ public void setChunkedUpload(String contentType) { |
+ synchronized (mLock) { |
+ validateNotStarted(); |
+ mUploadContentType = contentType; |
+ mUploadChannel = null; |
+ mUploadData = null; |
+ mAppendChunkSemaphore = new Semaphore(0); |
+ } |
+ } |
+ |
+ /** |
+ * Uploads a new chunk. |
+ * @param chunk The data. It must not be empty and its position must be zero. |
+ * @param isLastChunk Whether this chunk is the last one. |
+ */ |
+ // Invokes {@link #nativeAppendChunk(long, ByteBuffer, int, boolean)} and waits for it |
+ // to notify completion. |
+ public void appendChunkBlocking(ByteBuffer chunk, boolean isLastChunk) |
mef
2014/08/15 13:30:22
Due to limitations of our codereview tool we keep
mdumitrescu
2014/08/15 15:23:12
Done.
|
+ 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 (mAppendChunkSemaphore == null) { |
+ throw new IllegalArgumentException("This is not a chunked upload request."); |
+ } |
+ if (mUrlRequestAdapter == 0) { |
+ throw new IOException("Native peer destroyed."); |
+ } |
+ nativeAppendChunk(mUrlRequestAdapter, chunk, chunk.limit(), isLastChunk); |
+ // 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(); |
mef
2014/08/15 13:30:22
How does that work? Where would interrupt come fro
mdumitrescu
2014/08/15 15:23:12
Changed to using condition variable.
|
+ |
+ // TODO(miloslav): Not sure why do we set mSinkException here. |
+ mSinkException = new IOException("Upload interrupted", e); |
+ cancel(); |
+ throw mSinkException; |
mef
2014/08/15 13:30:22
Um, so where all those exceptions are getting caug
mdumitrescu
2014/08/15 15:23:13
Changed to using condition variable.
(The exceptio
|
+ } |
+ } |
+ } |
+ |
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 +208,15 @@ public class UrlRequest { |
validateNotRecycled(); |
mStarted = true; |
String method = mMethod; |
if (method == null && |
((mUploadData != null && mUploadData.length > 0) || |
- mUploadChannel != null)) { |
+ mUploadChannel != null || mAppendChunkSemaphore != 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 +239,17 @@ public class UrlRequest { |
if (mUploadData != null && mUploadData.length > 0) { |
nativeSetUploadData(mUrlRequestAdapter, mUploadContentType, |
mUploadData); |
} else if (mUploadChannel != null) { |
nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType, |
mUploadContentLength); |
+ } else if (mAppendChunkSemaphore != null) { |
+ nativeEnableChunkedUpload(mUrlRequestAdapter, |
+ mUploadContentType); |
} |
nativeStart(mUrlRequestAdapter); |
} |
} |
public void cancel() { |
@@ -284,14 +340,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() { |
+ mAppendChunkSemaphore.release(); |
+ } |
+ |
+ /** |
* 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 +390,17 @@ public class UrlRequest { |
* Notifies the listener, releases native data structures. |
*/ |
@SuppressWarnings("unused") |
@CalledByNative |
private void finish() { |
synchronized (mLock) { |
mFinished = true; |
+ if (mAppendChunkSemaphore != null) { |
+ mAppendChunkSemaphore.release(); |
+ } |
if (mRecycled) { |
return; |
} |
try { |
mSink.close(); |
} catch (IOException e) { |
@@ -421,14 +488,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, |
mef
2014/08/15 13:30:22
nit: limit line length to 80.
mdumitrescu
2014/08/15 15:23:12
Done.
|
+ 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); |
@@ -445,8 +518,9 @@ public class UrlRequest { |
private native void nativeGetAllHeaders(long urlRequestAdapter, |
ResponseHeadersMap headers); |
// Explicit class to work around JNI-generator generics confusion. |
private class ResponseHeadersMap extends HashMap<String, List<String>> { |
} |
+ |
mef
2014/08/15 13:30:22
nit: spurious nl?
mdumitrescu
2014/08/15 15:23:12
Done.
|
} |