Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Unified Diff: net/cronet/android/java/src/org/chromium/net/UrlRequest.java

Issue 145213003: Initial upload of cronet for Android. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Changed copyright year on sample code back to 2012 Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/base/request_priority.h ('k') | net/cronet/android/java/src/org/chromium/net/UrlRequestContext.java » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/cronet/android/java/src/org/chromium/net/UrlRequest.java
diff --git a/net/cronet/android/java/src/org/chromium/net/UrlRequest.java b/net/cronet/android/java/src/org/chromium/net/UrlRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..722498f1135b7b28e3d050b601b002b6be48a95b
--- /dev/null
+++ b/net/cronet/android/java/src/org/chromium/net/UrlRequest.java
@@ -0,0 +1,427 @@
+// Copyright 2013 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 org.apache.http.conn.ConnectTimeoutException;
+import org.chromium.base.AccessedByNative;
+import org.chromium.base.CalledByNative;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.Semaphore;
+
+/**
+ * Network request using the native http stack implementation.
+ */
+public class UrlRequest {
+ private static final class ContextLock {
+ }
+
+ /**
+ * Must match definitions in the native source.
+ */
+ public static final int REQUEST_PRIORITY_IDLE = 0;
+ public static final int REQUEST_PRIORITY_LOWEST = 1;
+ public static final int REQUEST_PRIORITY_LOW = 2;
+ public static final int REQUEST_PRIORITY_MEDIUM = 3;
+ public static final int REQUEST_PRIORITY_HIGHEST = 4;
+
+ /**
+ * Must match definitions in the native source.
+ */
+ private static final int SUCCESS = 0;
+ private static final int UNKNOWN_ERROR = 1;
+ private static final int MALFORMED_URL_ERROR = 2;
+ private static final int CONNECTION_TIMED_OUT = 3;
+ private static final int UNKNOWN_HOST = 4;
+
+ private static final int UPLOAD_BYTE_BUFFER_SIZE = 32768;
+
+ // TODO(mef) : Rename to follow Google Java style guide:
+ // In Google Style special prefixes or suffixes, like those seen in the
+ // examples name_, mName, s_name and kName, are not used.
+ private final UrlRequestContext mRequestContext;
+ private final String mUrl;
+ private final int mPriority;
+ private final Map<String, String> mHeaders;
+ private final WritableByteChannel mSink;
+
+ private Map<String, String> mAdditionalHeaders;
+ private boolean mPostBodySet;
+ private ReadableByteChannel mPostBodyChannel;
+ private WritableByteChannel mOutputChannel;
+ private IOException mSinkException;
+ private volatile boolean mStarted;
+ private volatile boolean mCanceled;
+ private volatile boolean mRecycled;
+ private volatile boolean mFinished;
+ private String mContentType;
+ private long mContentLength;
+ private Semaphore mAppendChunkSemaphore;
+ private final ContextLock mLock;
+
+ /**
+ * This field is accessed exclusively from the native layer.
+ */
+ @AccessedByNative
+ @SuppressWarnings("unused")
+ private long mRequest;
+
+ /**
+ * Constructor.
+ *
+ * @param requestContext The context.
+ * @param url The URL.
+ * @param priority Request priority, e.g. {@link #REQUEST_PRIORITY_MEDIUM}.
+ * @param headers HTTP headers.
+ * @param sink The output channel into which downloaded content will be
+ * written.
+ */
+ public UrlRequest(UrlRequestContext requestContext,
+ String url,
+ int priority,
+ Map<String, String> headers,
+ WritableByteChannel sink) {
+ mRequestContext = requestContext;
+ mUrl = url;
+ mPriority = priority;
+ mHeaders = headers;
+ mSink = sink;
+ mLock = new ContextLock();
+ nativeInit(mRequestContext, mUrl, mPriority);
+ mPostBodySet = false;
+ }
+
+ /**
+ * Adds a request header.
+ */
+ public void addHeader(String header, String value) {
+ validateNotStarted();
+ if (mAdditionalHeaders == null) {
+ mAdditionalHeaders = new HashMap<String, String>();
+ }
+ mAdditionalHeaders.put(header, value);
+ }
+
+ /**
+ * Sets data to upload as part of a POST request.
+ *
+ * @param contentType MIME type of the post content or null if this is not a
+ * POST.
+ * @param data The content that needs to be uploaded if this is a POST
+ * request.
+ */
+ public void setUploadData(String contentType, byte[] data) {
+ synchronized (mLock) {
+ validateNotStarted();
+ validatePostBodyNotSet();
+ nativeSetPostData(contentType, data);
+ mPostBodySet = true;
+ }
+ }
+
+ /**
+ * 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
+ * POST request.
+ * @param channel The channel to read to read upload data from if this is a
+ * POST request.
+ */
+ public void setUploadChannel(String contentType,
+ ReadableByteChannel channel) {
+ synchronized (mLock) {
+ validateNotStarted();
+ validatePostBodyNotSet();
+ nativeBeginChunkedUpload(contentType);
+ mPostBodyChannel = channel;
+ mPostBodySet = true;
+ }
+ mAppendChunkSemaphore = new Semaphore(0);
+ }
+
+ public WritableByteChannel getSink() {
+ return mSink;
+ }
+
+ public void start() {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return;
+ }
+
+ validateNotStarted();
+ validateNotRecycled();
+
+ mStarted = true;
+
+ if (mHeaders != null && !mHeaders.isEmpty()) {
+ for (Entry<String, String> entry : mHeaders.entrySet()) {
+ nativeAddHeader(entry.getKey(), entry.getValue());
+ }
+ }
+
+ if (mAdditionalHeaders != null && !mAdditionalHeaders.isEmpty()) {
+ for (Entry<String, String> entry : mAdditionalHeaders.entrySet()) {
+ nativeAddHeader(entry.getKey(), entry.getValue());
+ }
+ }
+
+ nativeStart();
+ }
+
+ if (mPostBodyChannel != null) {
+ uploadFromChannel(mPostBodyChannel);
+ }
+ }
+
+ /**
+ * 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(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();
+ } finally {
+ try {
+ mPostBodyChannel.close();
+ } catch (IOException ignore) {
+ ;
+ }
+ }
+ }
+
+ public void cancel() {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return;
+ }
+
+ mCanceled = true;
+
+ if (!mRecycled) {
+ nativeCancel();
+ }
+ }
+ }
+
+ public boolean isCanceled() {
+ synchronized (mLock) {
+ return mCanceled;
+ }
+ }
+
+ public boolean isRecycled() {
+ synchronized (mLock) {
+ return mRecycled;
+ }
+ }
+
+ /**
+ * Returns an exception if any, or null if the request was completed
+ * successfully.
+ */
+ public IOException getException() {
+ if (mSinkException != null) {
+ return mSinkException;
+ }
+
+ validateNotRecycled();
+
+ int errorCode = nativeGetErrorCode();
+ switch (errorCode) {
+ case SUCCESS:
+ return null;
+ case UNKNOWN_ERROR:
+ return new IOException(nativeGetErrorString());
+ case MALFORMED_URL_ERROR:
+ return new MalformedURLException("Malformed URL: " + mUrl);
+ case CONNECTION_TIMED_OUT:
+ return new ConnectTimeoutException("Connection timed out");
+ case UNKNOWN_HOST:
+ String host;
+ try {
+ host = new URL(mUrl).getHost();
+ } catch (MalformedURLException e) {
+ host = mUrl;
+ }
+ return new UnknownHostException("Unknown host: " + host);
+ default:
+ throw new IllegalStateException("Unrecognized error code: " +
+ errorCode);
+ }
+ }
+
+ public native int getHttpStatusCode();
+
+ /**
+ * Content length as reported by the server. May be -1 or incorrect if the
+ * server returns the wrong number, which happens even with Google servers.
+ */
+ public long getContentLength() {
+ return mContentLength;
+ }
+
+ public String getContentType() {
+ return mContentType;
+ }
+
+ /**
+ * 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();
+ mContentLength = nativeGetContentLength();
+ }
+
+ /**
+ * A callback invoked when the response has been fully consumed.
+ */
+ protected void onRequestComplete() {
+ }
+
+ /**
+ * Consumes a portion of the response.
+ *
+ * @param byteBuffer The ByteBuffer to append. Must be a direct buffer, and
+ * no references to it may be retained after the method ends, as it
+ * wraps code managed on the native heap.
+ */
+ @CalledByNative
+ protected void onBytesRead(ByteBuffer byteBuffer) {
+ try {
+ while (byteBuffer.hasRemaining()) {
+ mSink.write(byteBuffer);
+ }
+ } catch (IOException e) {
+ mSinkException = e;
+ cancel();
+ }
+ }
+
+ /**
+ * 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) {
+ // Ignore
+ }
+ onRequestComplete();
+ nativeRecycle();
+ mRecycled = true;
+ }
+ }
+
+ private void validateNotRecycled() {
+ if (mRecycled) {
+ throw new IllegalStateException("Accessing recycled request");
+ }
+ }
+
+ private void validateNotStarted() {
+ if (mStarted) {
+ throw new IllegalStateException("Request already started");
+ }
+ }
+
+ private void validatePostBodyNotSet() {
+ if (mPostBodySet) {
+ throw new IllegalStateException("Post Body already set");
+ }
+ }
+
+ public String getUrl() {
+ return mUrl;
+ }
+
+ private native void nativeInit(UrlRequestContext requestContext,
+ String url,
+ int priority);
+ private native void nativeAddHeader(String name, String value);
+ private native void nativeSetPostData(String contentType, byte[] content);
+ private native void nativeBeginChunkedUpload(String contentType);
+ private native void nativeAppendChunk(ByteBuffer chunk,
+ int chunkSize,
+ boolean isLastChunk);
+ private native void nativeStart();
+ private native void nativeCancel();
+ private native void nativeRecycle();
+ private native int nativeGetErrorCode();
+ private native String nativeGetErrorString();
+ private native String nativeGetContentType();
+ private native long nativeGetContentLength();
+}
« no previous file with comments | « net/base/request_priority.h ('k') | net/cronet/android/java/src/org/chromium/net/UrlRequestContext.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698