| Index: components/cronet/android/java/src/org/chromium/net/CronetUrlRequest.java
|
| diff --git a/components/cronet/android/java/src/org/chromium/net/CronetUrlRequest.java b/components/cronet/android/java/src/org/chromium/net/CronetUrlRequest.java
|
| index 3a7776dc8520cbabc219fde0c6515e7d1499e808..ee4fe1076f08a511a1cfb75ca6df4fabca8d1c97 100644
|
| --- a/components/cronet/android/java/src/org/chromium/net/CronetUrlRequest.java
|
| +++ b/components/cronet/android/java/src/org/chromium/net/CronetUrlRequest.java
|
| @@ -4,38 +4,169 @@
|
|
|
| package org.chromium.net;
|
|
|
| +import android.util.Log;
|
| +
|
| +import org.chromium.base.CalledByNative;
|
| +import org.chromium.base.JNINamespace;
|
| +
|
| +import java.net.URL;
|
| +import java.nio.ByteBuffer;
|
| +import java.util.ArrayList;
|
| +import java.util.HashMap;
|
| +import java.util.List;
|
| +import java.util.Map;
|
| +import java.util.concurrent.Executor;
|
| +
|
| /**
|
| * UrlRequest using Chromium HTTP stack implementation.
|
| */
|
| -public class CronetUrlRequest implements UrlRequest {
|
| +@JNINamespace("cronet")
|
| +final class CronetUrlRequest implements UrlRequest {
|
| + /** Native adapter object, owned by UrlRequest. */
|
| + private long mUrlRequestAdapter;
|
| + private final CronetUrlRequestContext mRequestContext;
|
| + private final List<String> mUrlChain = new ArrayList<String>();
|
| + private final int mPriority;
|
| + private final UrlRequestListener mListener;
|
| + private final Executor mExecutor;
|
| + private NativeResponseInfo mResponseInfo;
|
| + private boolean mStarted = false;
|
| + private boolean mCanceled = false;
|
| + private OnDataReceivedRunnable mOnDataReceivedTask;
|
| +
|
| + final class OnDataReceivedRunnable implements Runnable {
|
| + ByteBuffer mByteBuffer;
|
| + public void run() {
|
| + try {
|
| + mListener.onDataReceived(CronetUrlRequest.this,
|
| + mResponseInfo, mByteBuffer);
|
| + mByteBuffer = null;
|
| + if (!isCanceled())
|
| + nativeReceiveData(mUrlRequestAdapter);
|
| + } catch (Exception e) {
|
| + onCalledByNativeException(e);
|
| + }
|
| + }
|
| + }
|
| +
|
| + final class NativeResponseInfo implements ExtendedResponseInfo {
|
| + private final String[] mUrlChain;
|
| + private final int mHttpStatusCode;
|
| + private final ResponseHeadersMap mAllHeaders = new ResponseHeadersMap();
|
| + private final boolean mWasCached;
|
| + private final String mNegotiatedProtocol;
|
| + private long mTotalReceivedBytes = 0;
|
| +
|
| + NativeResponseInfo(String[] urlChain, int httpStatusCode,
|
| + boolean wasCached, String negotiatedProtocol) {
|
| + mUrlChain = urlChain;
|
| + mHttpStatusCode = httpStatusCode;
|
| + mWasCached = wasCached;
|
| + mNegotiatedProtocol = negotiatedProtocol;
|
| + }
|
| +
|
| + @Override
|
| + public String getUrl() {
|
| + return mUrlChain[mUrlChain.length - 1];
|
| + }
|
| +
|
| + @Override
|
| + public String[] getUrlChain() {
|
| + return mUrlChain;
|
| + }
|
| +
|
| + @Override
|
| + public int getHttpStatusCode() {
|
| + return mHttpStatusCode;
|
| + }
|
| +
|
| + @Override
|
| + public Map<String, List<String>> getAllHeaders() {
|
| + return mAllHeaders;
|
| + }
|
| +
|
| + @Override
|
| + public boolean wasCached() {
|
| + return mWasCached;
|
| + }
|
| +
|
| + @Override
|
| + public String getNegotiatedProtocol() {
|
| + return mNegotiatedProtocol;
|
| + }
|
| +
|
| + @Override
|
| + public long getTotalReceivedBytes() {
|
| + return mTotalReceivedBytes;
|
| + }
|
| + };
|
| +
|
| + CronetUrlRequest(CronetUrlRequestContext requestContext,
|
| + String url, int priority,
|
| + UrlRequestListener listener,
|
| + Executor executor) {
|
| + if (requestContext == null) {
|
| + throw new NullPointerException("Context is required");
|
| + }
|
| + if (url == null) {
|
| + throw new NullPointerException("URL is required");
|
| + }
|
| + if (listener == null) {
|
| + throw new NullPointerException("Listener is required");
|
| + }
|
| + if (executor == null) {
|
| + throw new NullPointerException("Executor is required");
|
| + }
|
| +
|
| + mRequestContext = requestContext;
|
| + mUrlChain.add(url);
|
| + mPriority = convertRequestPriority(priority);
|
| + mUrlRequestAdapter = nativeCreateRequestAdapter(
|
| + mRequestContext.getCronetUrlRequestContextAdapter(),
|
| + url,
|
| + mPriority);
|
| + mListener = listener;
|
| + mExecutor = executor;
|
| + }
|
| +
|
| @Override
|
| public void setHttpMethod(String method) {
|
| -
|
| + checkNotStarted();
|
| + if (method == null) {
|
| + throw new NullPointerException("method is required");
|
| + }
|
| + nativeSetHttpMethod(mUrlRequestAdapter, method);
|
| }
|
|
|
| @Override
|
| public void addHeader(String header, String value) {
|
| -
|
| + checkNotStarted();
|
| + if (header == null || value == null) {
|
| + throw new NullPointerException("Invalid header");
|
| + }
|
| + nativeAddHeader(mUrlRequestAdapter, header, value);
|
| }
|
|
|
| @Override
|
| - public void start(UrlRequestListener listener) {
|
| -
|
| + public void start() {
|
| + checkNotStarted();
|
| + mStarted = true;
|
| + nativeStart(mUrlRequestAdapter);
|
| }
|
|
|
| @Override
|
| public void cancel() {
|
| -
|
| + nativeCancel(mUrlRequestAdapter);
|
| }
|
|
|
| @Override
|
| public boolean isCanceled() {
|
| - return false;
|
| + return mCanceled;
|
| }
|
|
|
| @Override
|
| public void pause() {
|
| -
|
| + throw new UnsupportedOperationException("Not implemented yet");
|
| }
|
|
|
| @Override
|
| @@ -45,6 +176,228 @@ public class CronetUrlRequest implements UrlRequest {
|
|
|
| @Override
|
| public void resume() {
|
| + throw new UnsupportedOperationException("Not implemented yet");
|
| + }
|
| +
|
| + private static int convertRequestPriority(int priority) {
|
| + switch (priority) {
|
| + case REQUEST_PRIORITY_IDLE:
|
| + return ChromiumUrlRequestPriority.IDLE;
|
| + case REQUEST_PRIORITY_LOWEST:
|
| + return ChromiumUrlRequestPriority.LOWEST;
|
| + case REQUEST_PRIORITY_LOW:
|
| + return ChromiumUrlRequestPriority.LOW;
|
| + case REQUEST_PRIORITY_MEDIUM:
|
| + return ChromiumUrlRequestPriority.MEDIUM;
|
| + case REQUEST_PRIORITY_HIGHEST:
|
| + return ChromiumUrlRequestPriority.HIGHEST;
|
| + default:
|
| + return ChromiumUrlRequestPriority.MEDIUM;
|
| + }
|
| + }
|
| +
|
| + private NativeResponseInfo prepareResponseInfo() {
|
| + NativeResponseInfo responseInfo = new NativeResponseInfo(
|
| + mUrlChain.toArray(new String[mUrlChain.size()]),
|
| + nativeGetHttpStatusCode(mUrlRequestAdapter),
|
| + nativeGetWasCached(mUrlRequestAdapter),
|
| + nativeGetNegotiatedProtocol(mUrlRequestAdapter));
|
| + nativeGetAllHeaders(mUrlRequestAdapter, responseInfo.mAllHeaders);
|
| + return responseInfo;
|
| + }
|
| +
|
| + private void checkNotStarted() {
|
| + if (mStarted) {
|
| + throw new IllegalStateException("Request is already started.");
|
| + }
|
| + }
|
| +
|
| + // Private methods called by native library.
|
| +
|
| + /**
|
| + * If @CalledByNative method throws an exception, request gets cancelled
|
| + * and exception could be retrieved from request using getException().
|
| + */
|
| + private void onCalledByNativeException(Exception e) {
|
| + UrlRequestException requestError = new UrlRequestException(
|
| + "CalledByNative method has thrown an exception", e);
|
| + Log.e(CronetUrlRequestContext.LOG_TAG,
|
| + "Exception in CalledByNative method", e);
|
| + try {
|
| + mListener.onError(this, mResponseInfo, requestError);
|
| + cancel();
|
| + } catch (Exception cancelException) {
|
| + Log.e(CronetUrlRequestContext.LOG_TAG,
|
| + "Exception trying to cancel request", cancelException);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called before following redirects. The redirect will automatically be
|
| + * followed, unless the request is paused or cancelled during this
|
| + * callback. If the redirect response has a body, it will be ignored.
|
| + * This will only be called between start and onResponseStarted.
|
| + *
|
| + * @param info Response information.
|
| + * @param newLocation Location where request is redirected.
|
| + */
|
| + @SuppressWarnings("unused")
|
| + @CalledByNative
|
| + private void onRedirect(final String newLocation) {
|
| + mUrlChain.add(newLocation);
|
| + final NativeResponseInfo responseInfo = prepareResponseInfo();
|
| + Runnable task = new Runnable() {
|
| + public void run() {
|
| + try {
|
| + mListener.onRedirect(CronetUrlRequest.this, responseInfo,
|
| + new URL(newLocation));
|
| + if (!isCanceled())
|
| + nativeFollowDeferredRedirect(mUrlRequestAdapter);
|
| + } catch (Exception e) {
|
| + onCalledByNativeException(e);
|
| + }
|
| + }
|
| + };
|
| + mExecutor.execute(task);
|
| + }
|
| +
|
| + /**
|
| + * Called when the final set of headers, after all redirects,
|
| + * is received. Can only be called once for each request.
|
| + *
|
| + * @param info Response information.
|
| + */
|
| + @SuppressWarnings("unused")
|
| + @CalledByNative
|
| + private void onResponseStarted() {
|
| + mResponseInfo = prepareResponseInfo();
|
| + Runnable task = new Runnable() {
|
| + public void run() {
|
| + try {
|
| + mListener.onResponseStarted(CronetUrlRequest.this,
|
| + mResponseInfo);
|
| + if (!isCanceled())
|
| + nativeReceiveData(mUrlRequestAdapter);
|
| + } catch (Exception e) {
|
| + onCalledByNativeException(e);
|
| + }
|
| + }
|
| + };
|
| + mExecutor.execute(task);
|
| + }
|
| +
|
| + /**
|
| + * Called whenever data is received. The ByteBuffer remains
|
| + * valid only until listener callback. Or if the callback
|
| + * pauses the request, it remains valid until the request is resumed.
|
| + * Cancelling the request also invalidates the buffer.
|
| + *
|
| + * @param byteBuffer Received data.
|
| + */
|
| + @SuppressWarnings("unused")
|
| + @CalledByNative
|
| + private void onDataReceived(final ByteBuffer byteBuffer) {
|
| + if (mOnDataReceivedTask == null)
|
| + mOnDataReceivedTask = new OnDataReceivedRunnable();
|
| + mOnDataReceivedTask.mByteBuffer = byteBuffer;
|
| + mExecutor.execute(mOnDataReceivedTask);
|
| + }
|
| +
|
| + /**
|
| + * Called when request is complete, no callbacks will be called afterwards.
|
| + */
|
| + @SuppressWarnings("unused")
|
| + @CalledByNative
|
| + private void onComplete(final boolean canceled) {
|
| + mResponseInfo.mTotalReceivedBytes =
|
| + nativeGetTotalReceivedBytes(mUrlRequestAdapter);
|
| + Runnable task = new Runnable() {
|
| + public void run() {
|
| + mCanceled = canceled;
|
| + try {
|
| + mListener.onComplete(CronetUrlRequest.this, mResponseInfo);
|
| + nativeDestroyRequestAdapter(mUrlRequestAdapter);
|
| + } catch (Exception e) {
|
| + onCalledByNativeException(e);
|
| + }
|
| + }
|
| + };
|
| + mExecutor.execute(task);
|
| + }
|
| +
|
| + /**
|
| + * Called when error has occured, no callbacks will be called afterwards.
|
| + */
|
| + @SuppressWarnings("unused")
|
| + @CalledByNative
|
| + private void onError(final int error) {
|
| + Runnable task = new Runnable() {
|
| + public void run() {
|
| + try {
|
| + UrlRequestException requestError = new UrlRequestException(
|
| + "Exception in CronetUrlRequest ", error);
|
| + mListener.onError(CronetUrlRequest.this,
|
| + mResponseInfo,
|
| + requestError);
|
| + nativeDestroyRequestAdapter(mUrlRequestAdapter);
|
| + } catch (Exception e) {
|
| + onCalledByNativeException(e);
|
| + }
|
| + }
|
| + };
|
| + mExecutor.execute(task);
|
| + }
|
| +
|
| + /**
|
| + * Appends header |name| with value |value| to |headersMap|.
|
| + */
|
| + @SuppressWarnings("unused")
|
| + @CalledByNative
|
| + private void onAppendResponseHeader(ResponseHeadersMap headersMap,
|
| + String name, String value) {
|
| + try {
|
| + if (!headersMap.containsKey(name)) {
|
| + headersMap.put(name, new ArrayList<String>());
|
| + }
|
| + headersMap.get(name).add(value);
|
| + } catch (Exception e) {
|
| + onCalledByNativeException(e);
|
| + }
|
| + }
|
| +
|
| + // Native methods are implemented in cronet_url_request.cc.
|
| +
|
| + private native long nativeCreateRequestAdapter(
|
| + long urlRequestContextAdapter, String url, int priority);
|
| +
|
| + private native void nativeAddHeader(long urlRequestAdapter, String name,
|
| + String value);
|
| +
|
| + private native void nativeSetHttpMethod(long urlRequestAdapter,
|
| + String method);
|
| +
|
| + private native void nativeStart(long urlRequestAdapter);
|
| +
|
| + private native void nativeCancel(long urlRequestAdapter);
|
| +
|
| + private native void nativeDestroyRequestAdapter(long urlRequestAdapter);
|
| +
|
| + private native void nativeFollowDeferredRedirect(long urlRequestAdapter);
|
| +
|
| + private native void nativeReceiveData(long urlRequestAdapter);
|
| +
|
| + private native int nativeGetHttpStatusCode(long urlRequestAdapter);
|
| +
|
| + private native void nativeGetAllHeaders(long urlRequestAdapter,
|
| + ResponseHeadersMap headers);
|
| +
|
| + private native String nativeGetNegotiatedProtocol(long urlRequestAdapter);
|
| +
|
| + private native boolean nativeGetWasCached(long urlRequestAdapter);
|
| +
|
| + private native long nativeGetTotalReceivedBytes(long urlRequestAdapter);
|
|
|
| + // Explicit class to work around JNI-generator generics confusion.
|
| + private class ResponseHeadersMap extends HashMap<String, List<String>> {
|
| }
|
| }
|
|
|