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 da00fdd9a61fb912d17a3ca4e849019bc9aab927..0b8e45a9caa0d07b2c8571fb614c26aa6f91ea03 100644 |
--- a/components/cronet/android/java/src/org/chromium/net/UrlRequest.java |
+++ b/components/cronet/android/java/src/org/chromium/net/UrlRequest.java |
@@ -4,6 +4,8 @@ |
package org.chromium.net; |
+import android.util.Log; |
+ |
import org.apache.http.conn.ConnectTimeoutException; |
import org.chromium.base.CalledByNative; |
import org.chromium.base.JNINamespace; |
@@ -15,7 +17,9 @@ import java.net.UnknownHostException; |
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; |
@@ -25,6 +29,9 @@ import java.util.concurrent.Semaphore; |
*/ |
@JNINamespace("cronet") |
public class UrlRequest { |
+ private static final String TAG = "UrlRequest"; |
+ private static final boolean DBG = false; |
+ |
private static final class ContextLock { |
} |
@@ -123,14 +130,19 @@ public class UrlRequest { |
*/ |
public void setUploadChannel(String contentType, |
ReadableByteChannel channel) { |
+ throw new UnsupportedOperationException("Not implemented"); |
+ } |
+ |
+ public WritableByteChannel enableUpload(String contentType) { |
synchronized (mLock) { |
validateNotStarted(); |
validatePostBodyNotSet(); |
nativeBeginChunkedUpload(mUrlRequestPeer, contentType); |
- mPostBodyChannel = channel; |
+ //mPostBodyChannel = new UploadChannel(); |
mPostBodySet = true; |
} |
mAppendChunkSemaphore = new Semaphore(0); |
+ return new UploadChannel(); |
} |
public WritableByteChannel getSink() { |
@@ -167,9 +179,6 @@ public class UrlRequest { |
nativeStart(mUrlRequestPeer); |
} |
- if (mPostBodyChannel != null) { |
- uploadFromChannel(mPostBodyChannel); |
- } |
} finally { |
if (mPostBodyChannel != null) { |
try { |
@@ -181,56 +190,6 @@ public class UrlRequest { |
} |
} |
- /** |
- * Uploads data from a {@code ReadableByteChannel} using chunked transfer |
- * encoding. The native call to append a chunk is asynchronous so a |
- * semaphore is used to delay writing into the buffer again until chromium |
- * is finished with it. |
- * |
- * @param channel the channel to read data from. |
- */ |
- private void uploadFromChannel(ReadableByteChannel channel) { |
- ByteBuffer buffer = ByteBuffer.allocateDirect(UPLOAD_BYTE_BUFFER_SIZE); |
- |
- // The chromium API requires us to specify in advance if a chunk is the |
- // last one. This extra ByteBuffer is needed to peek ahead and check for |
- // the end of the channel. |
- ByteBuffer checkForEnd = ByteBuffer.allocate(1); |
- |
- try { |
- boolean lastChunk; |
- do { |
- // First dump in the one byte we read to check for the end of |
- // the channel. (The first time through the loop the checkForEnd |
- // buffer will be empty). |
- checkForEnd.flip(); |
- buffer.clear(); |
- buffer.put(checkForEnd); |
- checkForEnd.clear(); |
- |
- channel.read(buffer); |
- lastChunk = channel.read(checkForEnd) <= 0; |
- buffer.flip(); |
- nativeAppendChunk(mUrlRequestPeer, buffer, buffer.limit(), |
- lastChunk); |
- |
- if (lastChunk) { |
- break; |
- } |
- |
- // Acquire permit before writing to the buffer again to ensure |
- // chromium is done with it. |
- mAppendChunkSemaphore.acquire(); |
- } while (!lastChunk && !mFinished); |
- } catch (IOException e) { |
- mSinkException = e; |
- cancel(); |
- } catch (InterruptedException e) { |
- mSinkException = new IOException(e); |
- cancel(); |
- } |
- } |
- |
public void cancel() { |
synchronized (mLock) { |
if (mCanceled) { |
@@ -313,6 +272,23 @@ public class UrlRequest { |
return nativeGetHeader(mUrlRequestPeer, name); |
} |
+ // All response headers. |
+ public Map<String, List<String>> getAllHeaders() { |
+ validateHeadersAvailable(); |
+ String[] headers = nativeGetAllHeaders(mUrlRequestPeer); |
+ Map<String, List<String>> result = new HashMap<String, List<String>>(); |
+ for (int i = 0; i < headers.length / 2; ++i) { |
+ String key = headers[2 * i]; |
+ String value = headers[2 * i + 1]; |
+ if (!result.containsKey(key)) { |
+ result.put(key, new ArrayList<String>()); |
+ } |
+ result.get(key).add(value); |
+ } |
+ return result; |
+ } |
+ |
+ |
/** |
* A callback invoked when appending a chunk to the request has completed. |
*/ |
@@ -408,6 +384,39 @@ public class UrlRequest { |
} |
} |
+ /** |
+ * 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 (mUrlRequestPeer == 0) { |
+ throw new IOException("Native peer destroyed."); |
+ } |
+ nativeAppendChunk(mUrlRequestPeer, 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; |
+ } |
+ } |
+ } |
+ |
public String getUrl() { |
return mUrl; |
} |
@@ -444,4 +453,65 @@ public class UrlRequest { |
private native long nativeGetContentLength(long urlRequestPeer); |
private native String nativeGetHeader(long urlRequestPeer, String name); |
+ |
+ private native String[] nativeGetAllHeaders(long urlRequestPeer); |
+ |
+ |
+ class UploadChannel implements WritableByteChannel { |
+ |
+ 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 { |
+ if (DBG) Log.d(TAG, "UploadChannel.close() url=" + getUrl()); |
+ if (!mOpen) { |
+ return; |
+ } |
+ |
+ mOpen = false; |
+ mBuffer.clear(); |
+ |
+ // NOOP If the native peer has been destroyed. |
+ try { |
+ if (DBG) Log.d(TAG, "UploadChannel.close(): final chunk."); |
+ appendChunkBlocking(mBuffer, true); |
+ } catch (IOException e) { |
+ Log.w(TAG, "Ignoring exception during closing.", e); |
+ } |
+ |
+ if (DBG) Log.d(TAG, "UploadChannel.close() done."); |
+ } |
+ |
+ @Override |
+ public synchronized int write(ByteBuffer sourceBuffer) |
+ throws IOException { |
+ if (DBG) Log.d(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); |
+ } |
+ if (DBG) Log.d(TAG, "UploadChannel.write() returning"); |
+ return written; |
+ } |
+ |
+ } |
} |