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

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

Issue 458633002: Merge UrlRequest.java into ChromiumUrlRequest.java (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address clm's comments. Created 6 years, 4 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
Index: components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
diff --git a/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java b/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
index a643a1456230dc0d57adbba85a7644b460518b5a..db833d782842dcf26d6094659e6e6332da7ce776 100644
--- a/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
+++ b/components/cronet/android/java/src/org/chromium/net/ChromiumUrlRequest.java
@@ -4,35 +4,63 @@
package org.chromium.net;
+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;
+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;
/**
* Network request using the native http stack implementation.
*/
-public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest {
-
+@JNINamespace("cronet")
+public class ChromiumUrlRequest implements HttpUrlRequest {
+ /**
+ * Native adapter object, owned by UrlRequest.
+ */
+ private long mUrlRequestAdapter;
+ private final ChromiumUrlRequestContext 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 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 mUploadContentLength;
private final HttpUrlRequestListener mListener;
-
private boolean mBufferFullResponse;
-
private long mOffset;
-
private long mContentLength;
-
private long mContentLengthLimit;
-
private boolean mCancelIfContentLengthOverLimit;
-
private boolean mContentLengthOverLimit;
-
private boolean mSkippingToOffset;
-
private long mSize;
+ private final Object mLock = new Object();
- public ChromiumUrlRequest(UrlRequestContext requestContext,
+ public ChromiumUrlRequest(ChromiumUrlRequestContext requestContext,
String url, int priority, Map<String, String> headers,
HttpUrlRequestListener listener) {
this(requestContext, url, priority, headers,
@@ -40,29 +68,35 @@ public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest {
mBufferFullResponse = true;
}
- public ChromiumUrlRequest(UrlRequestContext requestContext,
+ /**
+ * 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 ChromiumUrlRequest(ChromiumUrlRequestContext requestContext,
String url, int priority, Map<String, String> headers,
WritableByteChannel sink, HttpUrlRequestListener listener) {
- super(requestContext, url, convertRequestPriority(priority), headers,
- sink);
- mListener = listener;
- }
-
- private static int convertRequestPriority(int priority) {
- switch (priority) {
- case HttpUrlRequest.REQUEST_PRIORITY_IDLE:
- return UrlRequestPriority.IDLE;
- case HttpUrlRequest.REQUEST_PRIORITY_LOWEST:
- return UrlRequestPriority.LOWEST;
- case HttpUrlRequest.REQUEST_PRIORITY_LOW:
- return UrlRequestPriority.LOW;
- case HttpUrlRequest.REQUEST_PRIORITY_MEDIUM:
- return UrlRequestPriority.MEDIUM;
- case HttpUrlRequest.REQUEST_PRIORITY_HIGHEST:
- return UrlRequestPriority.HIGHEST;
- default:
- return UrlRequestPriority.MEDIUM;
+ if (requestContext == null) {
+ throw new NullPointerException("Context is required");
}
+ if (url == null) {
+ throw new NullPointerException("URL is required");
+ }
+ mRequestContext = requestContext;
+ mUrl = url;
+ mPriority = convertRequestPriority(priority);
+ mHeaders = headers;
+ mSink = sink;
+ mUrlRequestAdapter = nativeCreateRequestAdapter(
+ mRequestContext.getChromiumUrlRequestContextAdapter(),
+ mUrl,
+ mPriority);
+ mListener = listener;
}
@Override
@@ -73,6 +107,13 @@ public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest {
}
}
+ /**
+ * The compressed content length as reported by the server. May be -1 if
+ * the server did not provide a length. Some servers may also report the
+ * wrong number. Since this is the compressed content length, and only
+ * uncompressed content is returned by the consumer, the consumer should
+ * not rely on this value.
+ */
@Override
public long getContentLength() {
return mContentLength;
@@ -85,10 +126,289 @@ public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest {
}
@Override
- protected void onResponseStarted() {
- super.onResponseStarted();
+ public int getHttpStatusCode() {
+ int httpStatusCode = nativeGetHttpStatusCode(mUrlRequestAdapter);
+
+ // TODO(mef): Investigate the following:
+ // If we have been able to successfully resume a previously interrupted
+ // download, the status code will be 206, not 200. Since the rest of the
+ // application is expecting 200 to indicate success, we need to fake it.
+ if (httpStatusCode == 206) {
+ httpStatusCode = 200;
+ }
+ return httpStatusCode;
+ }
+
+ /**
+ * Returns an exception if any, or null if the request was completed
+ * successfully.
+ */
+ @Override
+ public IOException getException() {
+ if (mSinkException != null) {
+ return mSinkException;
+ }
+
+ validateNotRecycled();
+
+ int errorCode = nativeGetErrorCode(mUrlRequestAdapter);
+ switch (errorCode) {
+ case ChromiumUrlRequestError.SUCCESS:
+ if (mContentLengthOverLimit) {
+ return new ResponseTooLargeException();
+ }
+ return null;
+ case ChromiumUrlRequestError.UNKNOWN:
+ return new IOException(
+ nativeGetErrorString(mUrlRequestAdapter));
+ case ChromiumUrlRequestError.MALFORMED_URL:
+ return new MalformedURLException("Malformed URL: " + mUrl);
+ case ChromiumUrlRequestError.CONNECTION_TIMED_OUT:
+ return new ConnectTimeoutException("Connection timed out");
+ case ChromiumUrlRequestError.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);
+ }
+ }
+
+ @Override
+ public ByteBuffer getByteBuffer() {
+ return ((ChunkedWritableByteChannel)getSink()).getByteBuffer();
+ }
+
+ @Override
+ public byte[] getResponseAsBytes() {
+ return ((ChunkedWritableByteChannel)getSink()).getBytes();
+ }
+
+ /**
+ * Adds a request header. Must be done before request has started.
+ */
+ public void addHeader(String header, String value) {
+ synchronized (mLock) {
+ validateNotStarted();
+ if (mAdditionalHeaders == null) {
+ mAdditionalHeaders = new HashMap<String, String>();
+ }
+ mAdditionalHeaders.put(header, value);
+ }
+ }
+
+ /**
+ * Sets data to upload as part of a POST or PUT request.
+ *
+ * @param contentType MIME type of the upload content or null if this is not
+ * an upload.
+ * @param data The content that needs to be uploaded.
+ */
+ public void setUploadData(String contentType, byte[] data) {
+ synchronized (mLock) {
+ validateNotStarted();
+ mUploadContentType = contentType;
+ mUploadData = data;
+ mUploadChannel = null;
+ }
+ }
+
+ /**
+ * Sets a readable byte channel to upload as part of a POST or PUT request.
+ *
+ * @param contentType MIME type of the upload content or null if this is not
+ * an upload request.
+ * @param channel The channel to read to read upload data from if this is an
+ * upload request.
+ * @param contentLength The length of data to upload.
+ */
+ public void setUploadChannel(String contentType,
+ ReadableByteChannel channel, long contentLength) {
+ synchronized (mLock) {
+ validateNotStarted();
+ mUploadContentType = contentType;
+ mUploadChannel = channel;
+ mUploadContentLength = contentLength;
+ mUploadData = null;
+ }
+ }
+
+ /**
+ * Sets HTTP method for upload request. Only PUT or POST are allowed.
+ */
+ public void setHttpMethod(String method) {
+ validateNotStarted();
+ if (!("PUT".equals(method) || "POST".equals(method))) {
+ throw new IllegalArgumentException("Only PUT or POST are allowed.");
+ }
+ mMethod = method;
+ }
+
+ public WritableByteChannel getSink() {
+ return mSink;
+ }
+
+ public void start() {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return;
+ }
+
+ validateNotStarted();
+ validateNotRecycled();
+
+ mStarted = true;
+
+ String method = mMethod;
+ if (method == null &&
+ ((mUploadData != null && mUploadData.length > 0) ||
+ mUploadChannel != null)) {
+ // Default to POST if there is data to upload but no method was
+ // specified.
+ method = "POST";
+ }
+
+ if (method != null) {
+ nativeSetMethod(mUrlRequestAdapter, method);
+ }
+
+ if (mHeaders != null && !mHeaders.isEmpty()) {
+ for (Entry<String, String> entry : mHeaders.entrySet()) {
+ nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ if (mAdditionalHeaders != null) {
+ for (Entry<String, String> entry :
+ mAdditionalHeaders.entrySet()) {
+ nativeAddHeader(mUrlRequestAdapter, entry.getKey(),
+ entry.getValue());
+ }
+ }
+
+ if (mUploadData != null && mUploadData.length > 0) {
+ nativeSetUploadData(mUrlRequestAdapter, mUploadContentType,
+ mUploadData);
+ } else if (mUploadChannel != null) {
+ nativeSetUploadChannel(mUrlRequestAdapter, mUploadContentType,
+ mUploadContentLength);
+ }
+
+ nativeStart(mUrlRequestAdapter);
+ }
+ }
+
+ public void cancel() {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return;
+ }
+
+ mCanceled = true;
+
+ if (!mRecycled) {
+ nativeCancel(mUrlRequestAdapter);
+ }
+ }
+ }
+
+ public boolean isCanceled() {
+ synchronized (mLock) {
+ return mCanceled;
+ }
+ }
+
+ public boolean isRecycled() {
+ synchronized (mLock) {
+ return mRecycled;
+ }
+ }
+
+ public String getContentType() {
+ return mContentType;
+ }
+
+ public String getHeader(String name) {
+ validateHeadersAvailable();
+ return nativeGetHeader(mUrlRequestAdapter, name);
+ }
+
+ // All response headers.
+ public Map<String, List<String>> getAllHeaders() {
+ validateHeadersAvailable();
+ ResponseHeadersMap result = new ResponseHeadersMap();
+ nativeGetAllHeaders(mUrlRequestAdapter, result);
+ return result;
+ }
+
+ public String getUrl() {
+ return mUrl;
+ }
+
+ private static int convertRequestPriority(int priority) {
+ switch (priority) {
+ case HttpUrlRequest.REQUEST_PRIORITY_IDLE:
+ return ChromiumUrlRequestPriority.IDLE;
+ case HttpUrlRequest.REQUEST_PRIORITY_LOWEST:
+ return ChromiumUrlRequestPriority.LOWEST;
+ case HttpUrlRequest.REQUEST_PRIORITY_LOW:
+ return ChromiumUrlRequestPriority.LOW;
+ case HttpUrlRequest.REQUEST_PRIORITY_MEDIUM:
+ return ChromiumUrlRequestPriority.MEDIUM;
+ case HttpUrlRequest.REQUEST_PRIORITY_HIGHEST:
+ return ChromiumUrlRequestPriority.HIGHEST;
+ default:
+ return ChromiumUrlRequestPriority.MEDIUM;
+ }
+ }
+
+ private void onContentLengthOverLimit() {
+ mContentLengthOverLimit = true;
+ cancel();
+ }
+
+ /**
+ * A callback invoked when the response has been fully consumed.
+ */
+ private void onRequestComplete() {
+ mListener.onRequestComplete(this);
+ }
+
+ private void validateNotRecycled() {
+ if (mRecycled) {
+ throw new IllegalStateException("Accessing recycled request");
+ }
+ }
+
+ private void validateNotStarted() {
+ if (mStarted) {
+ throw new IllegalStateException("Request already started");
+ }
+ }
+
+ private void validateHeadersAvailable() {
+ if (!mHeadersAvailable) {
+ throw new IllegalStateException("Response headers not available");
+ }
+ }
+
+ // Private methods called by native library.
+
+ /**
+ * A callback invoked when the first chunk of the response has arrived.
+ */
+ @CalledByNative
+ private void onResponseStarted() {
+ mContentType = nativeGetContentType(mUrlRequestAdapter);
+ mContentLength = nativeGetContentLength(mUrlRequestAdapter);
+ mHeadersAvailable = true;
- mContentLength = super.getContentLength();
if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit
&& mCancelIfContentLengthOverLimit) {
onContentLengthOverLimit();
@@ -102,8 +422,11 @@ public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest {
}
if (mOffset != 0) {
- // The server may ignore the request for a byte range.
- if (super.getHttpStatusCode() == 200) {
+ // The server may ignore the request for a byte range, in which case
+ // status code will be 200, instead of 206. Note that we cannot call
+ // getHttpStatusCode as it rewrites 206 into 200.
+ if (nativeGetHttpStatusCode(mUrlRequestAdapter) == 200) {
mef 2014/08/14 21:46:22 Using nativeGetHttpStatusCode avoids rewriting in
+ // TODO(mef): Revisit this logic.
if (mContentLength != -1) {
mContentLength -= mOffset;
}
@@ -115,8 +438,15 @@ public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest {
mListener.onResponseStarted(this);
}
- @Override
- protected void onBytesRead(ByteBuffer buffer) {
+ /**
+ * 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
+ private void onBytesRead(ByteBuffer buffer) {
if (mContentLengthOverLimit) {
return;
}
@@ -132,58 +462,130 @@ public class ChromiumUrlRequest extends UrlRequest implements HttpUrlRequest {
}
}
- if (mContentLengthLimit != 0 && mSize > mContentLengthLimit) {
+ boolean contentLengthOverLimit =
+ (mContentLengthLimit != 0 && mSize > mContentLengthLimit);
+ if (contentLengthOverLimit) {
buffer.limit(size - (int)(mSize - mContentLengthLimit));
- super.onBytesRead(buffer);
- onContentLengthOverLimit();
- return;
}
- super.onBytesRead(buffer);
+ try {
+ while (buffer.hasRemaining()) {
+ mSink.write(buffer);
+ }
+ } catch (IOException e) {
+ mSinkException = e;
+ cancel();
+ }
+ if (contentLengthOverLimit) {
+ onContentLengthOverLimit();
+ }
}
- private void onContentLengthOverLimit() {
- mContentLengthOverLimit = true;
- cancel();
- }
+ /**
+ * Notifies the listener, releases native data structures.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void finish() {
+ synchronized (mLock) {
+ mFinished = true;
- @Override
- protected void onRequestComplete() {
- mListener.onRequestComplete(this);
+ if (mRecycled) {
+ return;
+ }
+ try {
+ mSink.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ onRequestComplete();
+ nativeDestroyRequestAdapter(mUrlRequestAdapter);
+ mUrlRequestAdapter = 0;
+ mRecycled = true;
+ }
}
- @Override
- public int getHttpStatusCode() {
- int httpStatusCode = super.getHttpStatusCode();
-
- // TODO(mef): Investigate the following:
- // If we have been able to successfully resume a previously interrupted
- // download,
- // the status code will be 206, not 200. Since the rest of the
- // application is
- // expecting 200 to indicate success, we need to fake it.
- if (httpStatusCode == 206) {
- httpStatusCode = 200;
+ /**
+ * Appends header |name| with value |value| to |headersMap|.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void onAppendResponseHeader(ResponseHeadersMap headersMap,
+ String name, String value) {
+ if (!headersMap.containsKey(name)) {
+ headersMap.put(name, new ArrayList<String>());
}
- return httpStatusCode;
+ headersMap.get(name).add(value);
}
- @Override
- public IOException getException() {
- IOException ex = super.getException();
- if (ex == null && mContentLengthOverLimit) {
- ex = new ResponseTooLargeException();
+ /**
+ * Reads a sequence of bytes from upload channel into the given buffer.
+ * @param dest The buffer into which bytes are to be transferred.
+ * @return Returns number of bytes read (could be 0) or -1 and closes
+ * the channel if error occured.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private int readFromUploadChannel(ByteBuffer dest) {
+ if (mUploadChannel == null || !mUploadChannel.isOpen())
+ return -1;
+ try {
+ int result = mUploadChannel.read(dest);
+ if (result < 0) {
+ mUploadChannel.close();
+ return 0;
+ }
+ return result;
+ } catch (IOException e) {
+ mSinkException = e;
+ try {
+ mUploadChannel.close();
+ } catch (IOException ignored) {
+ // Ignore this exception.
+ }
+ cancel();
+ return -1;
}
- return ex;
}
- @Override
- public ByteBuffer getByteBuffer() {
- return ((ChunkedWritableByteChannel)getSink()).getByteBuffer();
- }
+ // Native methods are implemented in chromium_url_request.cc.
- @Override
- public byte[] getResponseAsBytes() {
- return ((ChunkedWritableByteChannel)getSink()).getBytes();
+ private native long nativeCreateRequestAdapter(
+ long ChromiumUrlRequestContextAdapter, String url, int priority);
+
+ private native void nativeAddHeader(long urlRequestAdapter, String name,
+ String value);
+
+ private native void nativeSetMethod(long urlRequestAdapter, String method);
+
+ private native void nativeSetUploadData(long urlRequestAdapter,
+ String contentType, byte[] content);
+
+ private native void nativeSetUploadChannel(long urlRequestAdapter,
+ String contentType, long contentLength);
+
+ private native void nativeStart(long urlRequestAdapter);
+
+ private native void nativeCancel(long urlRequestAdapter);
+
+ private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
+
+ private native int nativeGetErrorCode(long urlRequestAdapter);
+
+ private native int nativeGetHttpStatusCode(long urlRequestAdapter);
+
+ private native String nativeGetErrorString(long urlRequestAdapter);
+
+ private native String nativeGetContentType(long urlRequestAdapter);
+
+ private native long nativeGetContentLength(long urlRequestAdapter);
+
+ private native String nativeGetHeader(long urlRequestAdapter, String name);
+
+ 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>> {
}
}

Powered by Google App Engine
This is Rietveld 408576698