| 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..b44e4a2190a247c6bbac7588b5be2885c26c846f
|
| --- /dev/null
|
| +++ b/net/cronet/android/java/src/org/chromium/net/UrlRequest.java
|
| @@ -0,0 +1,286 @@
|
| +// Copyright 2014 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.WritableByteChannel;
|
| +import java.util.HashMap;
|
| +import java.util.Map;
|
| +import java.util.Map.Entry;
|
| +
|
| +/**
|
| + * Network request using the native http stack implementation.
|
| + */
|
| +public class UrlRequest {
|
| +
|
| + /*
|
| + * 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 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 String mPostBodyContentType;
|
| + private byte[] mPostBody;
|
| + private WritableByteChannel mOutputChannel;
|
| + private IOException mSinkException;
|
| + private boolean mStarted;
|
| + private boolean mCanceled;
|
| + private boolean mRecycled;
|
| + private String mContentType;
|
| + private long mContentLength;
|
| +
|
| + /**
|
| + * 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;
|
| + nativeInit(mRequestContext, mUrl, mPriority);
|
| + }
|
| +
|
| + /**
|
| + * 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) {
|
| + validateNotStarted();
|
| + mPostBodyContentType = contentType;
|
| + mPostBody = data;
|
| + }
|
| +
|
| + public WritableByteChannel getSink() {
|
| + return mSink;
|
| + }
|
| +
|
| + public synchronized void start() {
|
| + if (mCanceled) {
|
| + return;
|
| + }
|
| +
|
| + validateNotStarted();
|
| + validateNotRecycled();
|
| +
|
| + if (mPostBody != null && mPostBody.length > 0) {
|
| + nativeSetPostData(mPostBodyContentType, mPostBody);
|
| + }
|
| +
|
| + 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();
|
| + }
|
| +
|
| + public synchronized void cancel() {
|
| + if (mCanceled) {
|
| + return;
|
| + }
|
| +
|
| + mCanceled = true;
|
| +
|
| + if (!mRecycled) {
|
| + nativeCancel();
|
| + }
|
| + }
|
| +
|
| + public synchronized boolean isCanceled() {
|
| + return mCanceled;
|
| + }
|
| +
|
| + public synchronized boolean isRecycled() {
|
| + 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 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 synchronized void finish() {
|
| + 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");
|
| + }
|
| + }
|
| +
|
| + 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 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();
|
| +}
|
|
|