Chromium Code Reviews| 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.
|
| } |