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

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

Issue 183333002: Cronet Java wrappers to fallback to HttpUrlConnection if Cronet is not available. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 10 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: net/cronet/android/java/src/org/chromium/net/HttpUrlConnectionUrlRequest.java
diff --git a/net/cronet/android/java/src/org/chromium/net/HttpUrlConnectionUrlRequest.java b/net/cronet/android/java/src/org/chromium/net/HttpUrlConnectionUrlRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f621f44bdd1cb4733743370e524c10efe912e67e
--- /dev/null
+++ b/net/cronet/android/java/src/org/chromium/net/HttpUrlConnectionUrlRequest.java
@@ -0,0 +1,390 @@
+// 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 android.content.Context;
+import android.text.TextUtils;
+
+import org.apache.http.HttpStatus;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Network request using the HttpUrlConnection implementation.
+ */
+class HttpUrlConnectionUrlRequest implements HttpUrlRequest {
+
+ private static final int MAX_CHUNK_SIZE = 8192;
+
+ private static final int CONNECT_TIMEOUT = 3000;
+ private static final int READ_TIMEOUT = 90000;
+
+ private final Context mContext;
+ private final String mUrl;
+ private final Map<String, String> mHeaders;
+ private final WritableByteChannel mSink;
+ private final HttpUrlRequestListener mListener;
+ private IOException mException;
+ private HttpURLConnection mConnection;
+ private long mOffset;
+ private int mContentLength;
+ private long mContentLengthLimit;
+ private boolean mCancelIfContentLengthOverLimit;
+ private boolean mContentLengthOverLimit;
+ private boolean mSkippingToOffset;
+ private long mSize;
+ private String mPostContentType;
+ private byte[] mPostData;
+ private ReadableByteChannel mPostDataChannel;
+ private String mContentType;
+ private int mHttpStatusCode;
+ private boolean mStarted;
+ private boolean mCanceled;
+
+ private InputStream mResponseStream;
+
+ private static ExecutorService sExecutorService;
+
+ HttpUrlConnectionUrlRequest(Context context, String url, int requestPriority,
+ Map<String, String> headers, HttpUrlRequestListener listener) {
+ this(context, url, requestPriority, headers, new ChunkedWritableByteChannel(), listener);
+ }
+
+ HttpUrlConnectionUrlRequest(Context context, String url, int requestPriority,
+ Map<String, String> headers, WritableByteChannel sink,
+ HttpUrlRequestListener listener) {
+ if (context == null) {
+ throw new NullPointerException("Context is required");
+ }
+ if (url == null) {
+ throw new NullPointerException("URL is required");
+ }
+ mContext = context;
+ mUrl = url;
+ mHeaders = headers;
+ mSink = sink;
+ mListener = listener;
+ }
+
+ private static synchronized ExecutorService getExecutor() {
+ if (sExecutorService == null) {
+ ThreadFactory threadFactory = new ThreadFactory() {
+ private final AtomicInteger mCount = new AtomicInteger(1);
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = new Thread(r, "HttpUrlConnection #" + mCount.getAndIncrement());
+ // Note that this thread is not doing actual networking. It's only a controller.
+ thread.setPriority(Thread.NORM_PRIORITY);
+ return thread;
+ }
+ };
+ sExecutorService = Executors.newCachedThreadPool(threadFactory);
+ }
+ return sExecutorService;
+ }
+
+ @Override
+ public String getUrl() {
+ return mUrl;
+ }
+
+ @Override
+ public void setOffset(long offset) {
+ mOffset = offset;
+ }
+
+ @Override
+ public void setContentLengthLimit(long limit, boolean cancelEarly) {
+ mContentLengthLimit = limit;
+ mCancelIfContentLengthOverLimit = cancelEarly;
+ }
+
+ @Override
+ public void setUploadData(String contentType, byte[] data) {
+ validateNotStarted();
+ mPostContentType = contentType;
+ mPostData = data;
+ mPostDataChannel = null;
+ }
+
+ @Override
+ public void setUploadChannel(String contentType, ReadableByteChannel channel) {
+ validateNotStarted();
+ mPostContentType = contentType;
+ mPostDataChannel = channel;
+ mPostData = null;
+ }
+
+ @Override
+ public void start() {
+ boolean readingResponse = false;
+ try {
+ synchronized (this) {
+ if (mCanceled) {
+ return;
+ }
+ }
+
+ URL url = new URL(mUrl);
+ mConnection = (HttpURLConnection) url.openConnection();
+ mConnection.setConnectTimeout(CONNECT_TIMEOUT);
+ mConnection.setReadTimeout(READ_TIMEOUT);
+ mConnection.setInstanceFollowRedirects(true);
+ if (mHeaders != null) {
+ for (Entry<String, String> header : mHeaders.entrySet()) {
+ mConnection.setRequestProperty(header.getKey(), header.getValue());
+ }
+ }
+
+ if (mOffset != 0) {
+ mConnection.setRequestProperty("Range", "bytes=" + mOffset + "-");
+ }
+
+ if (mConnection.getRequestProperty("User-Agent") == null) {
+ mConnection.setRequestProperty("User-Agent", UserAgent.from(mContext));
+ }
+
+ if (mPostData != null || mPostDataChannel != null) {
+ uploadData();
+ }
+
+ InputStream stream = null;
+ try {
+ // We need to open the stream before asking for the response code, see http://b/12610546
+ stream = mConnection.getInputStream();
+ } catch (FileNotFoundException ex) {
+ // Ignore - the response has no body
+ }
+
+ mHttpStatusCode = mConnection.getResponseCode();
+ mContentType = mConnection.getContentType();
+ mContentLength = mConnection.getContentLength();
+ if (mContentLengthLimit > 0 && mContentLength > mContentLengthLimit
+ && mCancelIfContentLengthOverLimit) {
+ onContentLengthOverLimit();
+ return;
+ }
+
+ mResponseStream = isError(mHttpStatusCode) ? mConnection.getErrorStream() : stream;
+
+ if (mResponseStream != null && "gzip".equals(mConnection.getContentEncoding())) {
+ mResponseStream = new GZIPInputStream(mResponseStream);
+ mContentLength = -1;
+ }
+
+ if (mOffset != 0) {
+ // The server may ignore the request for a byte range
+ if (mHttpStatusCode == HttpStatus.SC_OK) {
+ if (mContentLength != -1) {
+ mContentLength -= mOffset;
+ }
+ mSkippingToOffset = true;
+ } else {
+ mSize = mOffset;
+ }
+ }
+
+ if (mResponseStream != null) {
+ readingResponse = true;
+ readResponseAsync();
+ }
+ } catch (IOException e) {
+ mException = e;
+ } finally {
+ // Don't call onRequestComplete yet if we are reading the response on a separate thread
+ if (!readingResponse) {
+ mListener.onRequestComplete(this);
+ }
+ }
+ }
+
+ private void uploadData() throws IOException {
+ mConnection.setDoOutput(true);
+ if (!TextUtils.isEmpty(mPostContentType)) {
+ mConnection.setRequestProperty("Content-Type", mPostContentType);
+ }
+
+ OutputStream uploadStream = null;
+ try {
+ if (mPostData != null) {
+ mConnection.setFixedLengthStreamingMode(mPostData.length);
+ uploadStream = mConnection.getOutputStream();
+ uploadStream.write(mPostData);
+ } else {
+ mConnection.setChunkedStreamingMode(MAX_CHUNK_SIZE);
+ uploadStream = mConnection.getOutputStream();
+ byte[] bytes = new byte[MAX_CHUNK_SIZE];
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ while (mPostDataChannel.read(byteBuffer) > 0) {
+ byteBuffer.flip();
+ uploadStream.write(bytes, 0, byteBuffer.limit());
+ byteBuffer.clear();
+ }
+ }
+ } finally {
+ if (uploadStream != null) {
+ uploadStream.close();
+ }
+ }
+ }
+
+ private void readResponseAsync() {
+ getExecutor().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ readResponse();
+ }
+ });
+ }
+
+ private void readResponse() {
+ try {
+ if (mResponseStream != null) {
+ readResponseStream();
+ }
+ } catch (IOException e) {
+ mException = e;
+ } finally {
+ try {
+ mConnection.disconnect();
+ } catch (ArrayIndexOutOfBoundsException t) {
+ // See http://b/11870814
+ }
+
+ try {
+ mSink.close();
+ } catch (IOException e) {
+ if (mException == null) {
+ mException = e;
+ }
+ }
+ }
+ mListener.onRequestComplete(this);
+ }
+
+ private void readResponseStream() throws IOException {
+ byte[] buffer = new byte[MAX_CHUNK_SIZE];
+ int size;
+ while (!mCanceled && (size = mResponseStream.read(buffer)) != -1) {
+ int start = 0;
+ int count = size;
+ mSize += size;
+ if (mSkippingToOffset) {
+ if (mSize <= mOffset) {
+ continue;
+ } else {
+ mSkippingToOffset = false;
+ start = (int) (mOffset - (mSize - size));
+ count -= start;
+ }
+ }
+
+ if (mContentLengthLimit != 0 && mSize > mContentLengthLimit) {
+ count -= (int) (mSize - mContentLengthLimit);
+ if (count > 0) {
+ mSink.write(ByteBuffer.wrap(buffer, start, count));
+ }
+ onContentLengthOverLimit();
+ return;
+ }
+
+ mSink.write(ByteBuffer.wrap(buffer, start, count));
+ }
+ }
+
+ @Override
+ public void cancel() {
+ synchronized (this) {
+ if (mCanceled) {
+ return;
+ }
+
+ mCanceled = true;
+ }
+ }
+
+ @Override
+ public boolean isCanceled() {
+ synchronized (this) {
+ return mCanceled;
+ }
+ }
+
+ @Override
+ public int getHttpStatusCode() {
+ int httpStatusCode = mHttpStatusCode;
+
+ // 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 == HttpStatus.SC_PARTIAL_CONTENT) {
+ httpStatusCode = HttpStatus.SC_OK;
+ }
+ return httpStatusCode;
+ }
+
+ @Override
+ public IOException getException() {
+ if (mException == null && mContentLengthOverLimit) {
+ mException = new ResponseTooLargeException();
+ }
+ return mException;
+ }
+
+ private void onContentLengthOverLimit() {
+ mContentLengthOverLimit = true;
+ cancel();
+ }
+
+ private static boolean isError(int statusCode) {
+ return (statusCode / 100) != 2;
+ }
+
+ /**
+ * Returns the response as a ByteBuffer.
+ */
+ @Override
+ public ByteBuffer getByteBuffer() {
+ return ((ChunkedWritableByteChannel) mSink).getByteBuffer();
+ }
+
+ @Override
+ public byte[] getResponseAsBytes() {
+ return ((ChunkedWritableByteChannel) mSink).getBytes();
+ }
+
+ @Override
+ public long getContentLength() {
+ return mContentLength;
+ }
+
+ @Override
+ public String getContentType() {
+ return mContentType;
+ }
+
+ private void validateNotStarted() {
+ if (mStarted) {
+ throw new IllegalStateException("Request already started");
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698