Index: components/cronet/android/java/src/org/chromium/net/CronetBidirectionalStream.java |
diff --git a/components/cronet/android/java/src/org/chromium/net/CronetBidirectionalStream.java b/components/cronet/android/java/src/org/chromium/net/CronetBidirectionalStream.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fad8fe79cf8ba33811718ee28dc7a6e843623363 |
--- /dev/null |
+++ b/components/cronet/android/java/src/org/chromium/net/CronetBidirectionalStream.java |
@@ -0,0 +1,634 @@ |
+// Copyright 2015 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.chromium.base.Log; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.base.annotations.CalledByNative; |
+import org.chromium.base.annotations.JNINamespace; |
+import org.chromium.base.annotations.NativeClassQualifiedName; |
+ |
+import java.nio.ByteBuffer; |
+import java.util.AbstractMap; |
+import java.util.ArrayList; |
+import java.util.List; |
+import java.util.Map; |
+import java.util.concurrent.Executor; |
+import java.util.concurrent.RejectedExecutionException; |
+ |
+import javax.annotation.concurrent.GuardedBy; |
+ |
+/** |
+ * {@link BidirectionalStream} implementation using Chromium network stack. |
+ * All @CalledByNative methods are called on the native network thread |
+ * and post tasks with callback calls onto Executor. Upon return from callback, the native |
xunjieli
2016/01/22 16:45:20
s/Upon return/Upon returning
mef
2016/01/22 17:36:07
Done.
|
+ * stream is called on Executor thread and posts native tasks to the native network thread. |
+ */ |
+@JNINamespace("cronet") |
+class CronetBidirectionalStream extends BidirectionalStream { |
+ /** |
+ * States of BidirectionalStream are tracked in mReadState and mWriteState. |
+ * The write state is separated out as it changes independently of the read state. |
+ * There is one initial state: State.NOT_STARTED. There is one normal final state: |
+ * State.SUCCESS, reached after State.READING_DONE and State.WRITING_DONE. There are two |
+ * exceptional final states: State.CANCELED and State.ERROR, which can be reached from |
+ * any other non-final state. |
+ */ |
+ private enum State { |
+ /* Initial state, stream not started. */ |
+ NOT_STARTED, |
+ /* Stream started, request headers are being sent. */ |
+ STARTED, |
+ /* Waiting for {@code read()} to be called. */ |
+ WAITING_FOR_READ, |
+ /* Reading from the remote, {@code onReadCompleted()} callback will be called when done. */ |
+ READING, |
+ /* There is no more data to read and stream is half-closed by the remote side. */ |
+ READING_DONE, |
+ /* Stream is canceled. */ |
+ CANCELED, |
+ /* Error has occured, stream is closed. */ |
+ ERROR, |
+ /* Reading and writing are done, and the stream is closed successfully. */ |
+ SUCCESS, |
+ /* Waiting for {@code write()} to be called. */ |
+ WAITING_FOR_WRITE, |
+ /* Writing to the remote, {@code onWriteCompleted()} callback will be called when done. */ |
+ WRITING, |
+ /* Writing the last frame, so {@code State.WRITING_DONE} will be set upon completion. */ |
+ WRITING_END_OF_STREAM, |
+ /* There is no more data to write and stream is half-closed by the local side. */ |
+ WRITING_DONE, |
+ } |
+ |
+ private final CronetUrlRequestContext mRequestContext; |
+ private final Executor mExecutor; |
+ private final Callback mCallback; |
+ private final String mInitialUrl; |
+ private final int mInitialPriority; |
+ private final String mInitialMethod; |
+ private final String mRequestHeaders[]; |
+ |
+ /* |
+ * Synchronizes access to mNativeStream, mReadState and mWriteState. |
+ */ |
+ private final Object mNativeStreamLock = new Object(); |
+ |
+ /* Native BidirectionalStream object, owned by CronetBidirectionalStream. */ |
+ @GuardedBy("mNativeStreamLock") private long mNativeStream; |
+ |
+ /** |
+ * Read state is tracking reading flow. |
+ * / <--- READING <--- \ |
+ * | | |
+ * \ / |
+ * NOT_STARTED -> STARTED --> WAITING_FOR_READ -> READING_DONE -> SUCCESS |
+ */ |
+ @GuardedBy("mNativeStreamLock") private State mReadState = State.NOT_STARTED; |
+ |
+ /** |
+ * Write state is tracking writing flow. |
+ * / <--- WRITING <--- \ |
+ * | | |
+ * \ / |
+ * NOT_STARTED -> STARTED --> WAITING_FOR_WRITE -> WRITING_END_OF_STREAM -> WRITING_DONE -> |
+ * SUCCESS |
+ */ |
+ @GuardedBy("mNativeStreamLock") private State mWriteState = State.NOT_STARTED; |
+ |
+ private UrlResponseInfo mResponseInfo; |
+ |
+ /* |
+ * OnReadCompleted callback is repeatedly invoked when each read is completed, so it |
+ * is cached as a member variable. |
+ */ |
+ private OnReadCompletedRunnable mOnReadCompletedTask; |
+ |
+ /* |
+ * OnWriteCompleted callback is repeatedly invoked when each write is completed, so it |
+ * is cached as a member variable. |
+ */ |
+ private OnWriteCompletedRunnable mOnWriteCompletedTask; |
+ |
+ private Runnable mOnDestroyedCallbackForTesting; |
+ |
+ private final class OnReadCompletedRunnable implements Runnable { |
+ // Buffer passed back from current invocation of onReadCompleted. |
+ ByteBuffer mByteBuffer; |
+ // End of stream flag from current invocation of onReadCompleted. |
+ boolean mEndOfStream; |
+ |
+ @Override |
+ public void run() { |
+ try { |
+ // Null out mByteBuffer, to pass buffer ownership to callback or release if done. |
+ ByteBuffer buffer = mByteBuffer; |
+ mByteBuffer = null; |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ if (mEndOfStream) { |
+ mReadState = State.READING_DONE; |
+ if (maybeSucceededLocked()) { |
+ return; |
+ } |
+ } else { |
+ mReadState = State.WAITING_FOR_READ; |
+ } |
+ } |
+ mCallback.onReadCompleted(CronetBidirectionalStream.this, mResponseInfo, buffer); |
+ } catch (Exception e) { |
+ onCallbackException(e); |
+ } |
+ } |
+ } |
+ |
+ private final class OnWriteCompletedRunnable implements Runnable { |
+ // Buffer passed back from current invocation of onWriteCompleted. |
+ ByteBuffer mByteBuffer; |
+ |
+ @Override |
+ public void run() { |
+ try { |
+ // Null out mByteBuffer, to pass buffer ownership to callback or release if done. |
+ ByteBuffer buffer = mByteBuffer; |
+ mByteBuffer = null; |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ if (mWriteState == State.WRITING_END_OF_STREAM) { |
xunjieli
2016/01/22 16:45:20
Is it possible to get rid of WRITING_END_OF_STREAM
mef
2016/01/22 17:36:07
Hrm, that could work if we create OnWriteCompleted
xunjieli
2016/01/22 22:22:38
I don't see why not from the first glance, though
|
+ mWriteState = State.WRITING_DONE; |
+ if (maybeSucceededLocked()) { |
+ return; |
+ } |
+ } else { |
+ mWriteState = State.WAITING_FOR_WRITE; |
+ } |
+ } |
+ mCallback.onWriteCompleted(CronetBidirectionalStream.this, mResponseInfo, buffer); |
+ } catch (Exception e) { |
+ onCallbackException(e); |
+ } |
+ } |
+ } |
+ |
+ CronetBidirectionalStream(CronetUrlRequestContext requestContext, String url, |
+ @BidirectionalStream.Builder.StreamPriority int priority, Callback callback, |
+ Executor executor, String httpMethod, List<Map.Entry<String, String>> requestHeaders) { |
+ mRequestContext = requestContext; |
+ mInitialUrl = url; |
+ mInitialPriority = convertStreamPriority(priority); |
+ mCallback = callback; |
+ mExecutor = executor; |
+ mInitialMethod = httpMethod; |
+ mRequestHeaders = stringsFromHeaderList(requestHeaders); |
+ } |
+ |
+ @Override |
+ public void start() { |
+ synchronized (mNativeStreamLock) { |
+ if (mReadState != State.NOT_STARTED) { |
+ throw new IllegalStateException("Stream is already started."); |
+ } |
+ try { |
+ mNativeStream = nativeCreateBidirectionalStream( |
+ mRequestContext.getUrlRequestContextAdapter()); |
+ mRequestContext.onRequestStarted(); |
+ // Non-zero startResult means an argument error. |
+ int startResult = nativeStart(mNativeStream, mInitialUrl, mInitialPriority, |
+ mInitialMethod, mRequestHeaders, !doesMethodAllowWriteData(mInitialMethod)); |
+ if (startResult == -1) { |
+ throw new IllegalArgumentException("Invalid http method " + mInitialMethod); |
+ } |
+ if (startResult > 0) { |
+ int headerPos = startResult - 1; |
+ throw new IllegalArgumentException("Invalid header " |
+ + mRequestHeaders[headerPos] + "=" + mRequestHeaders[headerPos + 1]); |
+ } |
+ mReadState = mWriteState = State.STARTED; |
+ } catch (RuntimeException e) { |
+ // If there's an exception, clean up and then throw the |
+ // exception to the caller. |
+ destroyNativeStreamLocked(false); |
+ throw e; |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void read(ByteBuffer buffer) { |
+ synchronized (mNativeStreamLock) { |
+ Preconditions.checkHasRemaining(buffer); |
+ Preconditions.checkDirect(buffer); |
+ if (mReadState != State.WAITING_FOR_READ) { |
+ throw new IllegalStateException("Unexpected read attempt."); |
+ } |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ mReadState = State.READING; |
+ if (!nativeReadData(mNativeStream, buffer, buffer.position(), buffer.limit())) { |
+ // Still waiting on read. This is just to have consistent |
+ // behavior with the other error cases. |
+ mReadState = State.WAITING_FOR_READ; |
+ throw new IllegalArgumentException("Unable to call native read"); |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void write(ByteBuffer buffer, boolean endOfStream) { |
+ synchronized (mNativeStreamLock) { |
+ Preconditions.checkDirect(buffer); |
+ if (!buffer.hasRemaining() && !endOfStream) { |
+ throw new IllegalArgumentException("Empty buffer before end of stream."); |
+ } |
+ if (mWriteState != State.WAITING_FOR_WRITE) { |
+ throw new IllegalStateException("Unexpected write attempt."); |
+ } |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ mWriteState = endOfStream ? State.WRITING_END_OF_STREAM : State.WRITING; |
+ if (!nativeWriteData( |
+ mNativeStream, buffer, buffer.position(), buffer.limit(), endOfStream)) { |
+ // Still waiting on write. This is just to have consistent |
+ // behavior with the other error cases. |
+ mWriteState = State.WAITING_FOR_WRITE; |
+ throw new IllegalArgumentException("Unable to call native write"); |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void ping(PingCallback callback, Executor executor) { |
+ // TODO(mef): May be last thing to be implemented on Android. |
+ throw new UnsupportedOperationException("ping is not supported yet."); |
+ } |
+ |
+ @Override |
+ public void windowUpdate(int windowSizeIncrement) { |
+ // TODO(mef): Understand the needs and semantics of this method. |
+ throw new UnsupportedOperationException("windowUpdate is not supported yet."); |
+ } |
+ |
+ @Override |
+ public void cancel() { |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked() || mReadState == State.NOT_STARTED) { |
+ return; |
+ } |
+ mReadState = mWriteState = State.CANCELED; |
+ destroyNativeStreamLocked(true); |
+ } |
+ } |
+ |
+ @Override |
+ public boolean isDone() { |
+ synchronized (mNativeStreamLock) { |
+ return isDoneLocked(); |
+ } |
+ } |
+ |
+ @GuardedBy("mNativeStreamLock") |
+ private boolean isDoneLocked() { |
+ return mReadState != State.NOT_STARTED && mNativeStream == 0; |
kapishnikov
2016/01/22 16:58:14
Should we protect the read with synchronized(mNati
mef
2016/01/22 17:36:06
Yep, it is guarded by mNativeStreamLock and always
|
+ } |
+ |
+ @SuppressWarnings("unused") |
+ @CalledByNative |
+ private void onRequestHeadersSent() { |
+ postTaskToExecutor(new Runnable() { |
+ public void run() { |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ if (doesMethodAllowWriteData(mInitialMethod)) { |
+ mWriteState = State.WAITING_FOR_WRITE; |
+ } else { |
+ mWriteState = State.WRITING_DONE; |
+ } |
+ } |
+ |
+ try { |
+ mCallback.onRequestHeadersSent(CronetBidirectionalStream.this); |
+ } catch (Exception e) { |
+ onCallbackException(e); |
+ } |
+ } |
+ }); |
+ } |
+ |
+ /** |
+ * Called when the final set of headers, after all redirects, |
+ * is received. Can only be called once for each stream. |
+ */ |
+ @SuppressWarnings("unused") |
+ @CalledByNative |
+ private void onResponseHeadersReceived(int httpStatusCode, String negotiatedProtocol, |
+ String[] headers, long receivedBytesCount) { |
+ mResponseInfo = |
+ prepareResponseInfoOnNetworkThread(httpStatusCode, negotiatedProtocol, headers); |
kapishnikov
2016/01/22 16:58:14
prepareResponseInfoOnNetworkThread() method can re
mef
2016/01/22 17:36:07
Done.
|
+ mResponseInfo.setReceivedBytesCount(receivedBytesCount); |
+ postTaskToExecutor(new Runnable() { |
+ public void run() { |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ mReadState = State.WAITING_FOR_READ; |
+ } |
+ |
+ try { |
+ mCallback.onResponseHeadersReceived( |
+ CronetBidirectionalStream.this, mResponseInfo); |
+ } catch (Exception e) { |
+ onCallbackException(e); |
+ } |
+ } |
+ }); |
+ } |
+ |
+ @SuppressWarnings("unused") |
+ @CalledByNative |
+ private void onReadCompleted(final ByteBuffer byteBuffer, int bytesRead, int initialPosition, |
+ int initialLimit, long receivedBytesCount) { |
+ mResponseInfo.setReceivedBytesCount(receivedBytesCount); |
+ if (byteBuffer.position() != initialPosition || byteBuffer.limit() != initialLimit) { |
+ failWithException( |
+ new CronetException("ByteBuffer modified externally during read", null)); |
+ return; |
+ } |
+ if (bytesRead < 0 || initialPosition + bytesRead > initialLimit) { |
+ failWithException(new CronetException("Invalid number of bytes read", null)); |
+ return; |
+ } |
+ if (mOnReadCompletedTask == null) { |
+ mOnReadCompletedTask = new OnReadCompletedRunnable(); |
+ } |
+ byteBuffer.position(initialPosition + bytesRead); |
+ mOnReadCompletedTask.mByteBuffer = byteBuffer; |
+ mOnReadCompletedTask.mEndOfStream = (bytesRead == 0); |
+ postTaskToExecutor(mOnReadCompletedTask); |
+ } |
+ |
+ @SuppressWarnings("unused") |
+ @CalledByNative |
+ private void onWriteCompleted( |
+ final ByteBuffer byteBuffer, int initialPosition, int initialLimit) { |
+ if (byteBuffer.position() != initialPosition || byteBuffer.limit() != initialLimit) { |
+ failWithException( |
+ new CronetException("ByteBuffer modified externally during write", null)); |
+ return; |
+ } |
+ if (mOnWriteCompletedTask == null) { |
+ mOnWriteCompletedTask = new OnWriteCompletedRunnable(); |
+ } |
+ // Current implementation always writes the complete buffer. |
+ byteBuffer.position(byteBuffer.limit()); |
+ mOnWriteCompletedTask.mByteBuffer = byteBuffer; |
+ postTaskToExecutor(mOnWriteCompletedTask); |
+ } |
+ |
+ @SuppressWarnings("unused") |
+ @CalledByNative |
+ private void onResponseTrailersReceived(String[] trailers) { |
+ final UrlResponseInfo.HeaderBlock trailersBlock = |
+ new UrlResponseInfo.HeaderBlock(headersListFromStrings(trailers)); |
+ postTaskToExecutor(new Runnable() { |
+ public void run() { |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ } |
+ try { |
+ mCallback.onResponseTrailersReceived( |
+ CronetBidirectionalStream.this, mResponseInfo, trailersBlock); |
+ } catch (Exception e) { |
+ onCallbackException(e); |
+ } |
+ } |
+ }); |
+ } |
+ |
+ @SuppressWarnings("unused") |
+ @CalledByNative |
+ private void onError(final int nativeError, final String errorString, long receivedBytesCount) { |
+ if (mResponseInfo != null) { |
+ mResponseInfo.setReceivedBytesCount(receivedBytesCount); |
+ } |
+ failWithException(new CronetException( |
+ "Exception in BidirectionalStream: " + errorString, nativeError)); |
+ } |
+ |
+ /** |
+ * Called when request is canceled, no callbacks will be called afterwards. |
+ */ |
+ @SuppressWarnings("unused") |
+ @CalledByNative |
+ private void onCanceled() { |
+ postTaskToExecutor(new Runnable() { |
+ public void run() { |
+ try { |
+ mCallback.onCanceled(CronetBidirectionalStream.this, mResponseInfo); |
+ } catch (Exception e) { |
+ Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onCanceled method", e); |
+ } |
+ } |
+ }); |
+ } |
+ |
+ @VisibleForTesting |
+ public void setOnDestroyedCallbackForTesting(Runnable onDestroyedCallbackForTesting) { |
+ mOnDestroyedCallbackForTesting = onDestroyedCallbackForTesting; |
+ } |
+ |
+ @GuardedBy("mNativeStreamLock") |
+ private boolean maybeSucceededLocked() { |
+ if (mReadState != State.READING_DONE || mWriteState != State.WRITING_DONE) { |
+ return false; |
+ } |
+ |
+ mReadState = mWriteState = State.SUCCESS; |
+ postTaskToExecutor(new Runnable() { |
+ public void run() { |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ // Destroy native stream first, so UrlRequestContext could be shut |
+ // down from the listener. |
+ destroyNativeStreamLocked(false); |
+ } |
+ try { |
+ mCallback.onSucceeded(CronetBidirectionalStream.this, mResponseInfo); |
+ } catch (Exception e) { |
+ Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onSucceeded method", e); |
+ } |
+ } |
+ }); |
+ return true; |
+ } |
+ |
+ private static boolean doesMethodAllowWriteData(String methodName) { |
+ return !methodName.equals("GET") && !methodName.equals("HEAD"); |
+ } |
+ |
+ /** |
+ * Posts task to application Executor. Used for callbacks |
+ * and other tasks that should not be executed on network thread. |
+ */ |
+ private void postTaskToExecutor(Runnable task) { |
+ try { |
+ mExecutor.execute(task); |
+ } catch (RejectedExecutionException failException) { |
+ Log.e(CronetUrlRequestContext.LOG_TAG, "Exception posting task to executor", |
+ failException); |
+ // If posting a task throws an exception, then there is no choice |
+ // but to destroy the stream without invoking the callback. |
+ synchronized (mNativeStreamLock) { |
+ mReadState = mWriteState = State.ERROR; |
+ destroyNativeStreamLocked(false); |
+ } |
+ } |
+ } |
+ |
+ private static ArrayList<Map.Entry<String, String>> headersListFromStrings(String[] headers) { |
+ ArrayList<Map.Entry<String, String>> headersList = |
+ new ArrayList<Map.Entry<String, String>>(headers.length / 2); |
kapishnikov
2016/01/22 16:58:14
Can we use 'diamond' syntax here, i.e. instead of
mef
2016/01/22 17:36:06
Done. Cute!
|
+ for (int i = 0; i < headers.length; i += 2) { |
+ headersList.add(new AbstractMap.SimpleImmutableEntry<String, String>( |
kapishnikov
2016/01/22 16:58:14
Same here.
mef
2016/01/22 17:36:07
Done.
|
+ headers[i], headers[i + 1])); |
+ } |
+ return headersList; |
+ } |
+ |
+ private static String[] stringsFromHeaderList(List<Map.Entry<String, String>> headersList) { |
xunjieli
2016/01/22 16:12:18
Although there is no official rule on class member
mef
2016/01/22 17:36:07
Done.
|
+ String headersArray[] = new String[headersList.size() * 2]; |
+ int i = 0; |
+ for (Map.Entry<String, String> requestHeader : headersList) { |
+ headersArray[i++] = requestHeader.getKey(); |
+ headersArray[i++] = requestHeader.getValue(); |
+ } |
+ return headersArray; |
+ } |
+ |
+ private UrlResponseInfo prepareResponseInfoOnNetworkThread( |
+ int httpStatusCode, String negotiatedProtocol, String[] headers) { |
+ synchronized (mNativeStreamLock) { |
+ if (mNativeStream == 0) { |
+ return null; |
+ } |
+ } |
+ |
+ ArrayList<String> urlChain = new ArrayList<String>(); |
kapishnikov
2016/01/22 16:58:14
Same here.
mef
2016/01/22 17:36:07
Done.
|
+ urlChain.add(mInitialUrl); |
+ |
+ boolean wasCached = false; |
+ String httpStatusText = ""; |
+ String proxyServer = null; |
+ |
+ UrlResponseInfo responseInfo = new UrlResponseInfo(urlChain, httpStatusCode, httpStatusText, |
+ headersListFromStrings(headers), wasCached, negotiatedProtocol, proxyServer); |
kapishnikov
2016/01/22 16:58:14
wasCached is always 'false'. httpStatusText is alw
mef
2016/01/22 17:36:07
Done.
|
+ return responseInfo; |
+ } |
+ |
+ private static int convertStreamPriority( |
+ @BidirectionalStream.Builder.StreamPriority int priority) { |
+ switch (priority) { |
+ case Builder.STREAM_PRIORITY_IDLE: |
+ return RequestPriority.IDLE; |
+ case Builder.STREAM_PRIORITY_LOWEST: |
+ return RequestPriority.LOWEST; |
+ case Builder.STREAM_PRIORITY_LOW: |
+ return RequestPriority.LOW; |
+ case Builder.STREAM_PRIORITY_MEDIUM: |
+ return RequestPriority.MEDIUM; |
+ case Builder.STREAM_PRIORITY_HIGHEST: |
+ return RequestPriority.HIGHEST; |
+ default: |
+ throw new IllegalArgumentException("Invalid stream priority."); |
+ } |
+ } |
+ |
+ @GuardedBy("mNativeStreamLock") |
+ private void destroyNativeStreamLocked(boolean sendOnCanceled) { |
+ Log.i(CronetUrlRequestContext.LOG_TAG, "destroyNativeStreamLocked " + this.toString()); |
+ if (mNativeStream == 0) { |
+ return; |
+ } |
+ nativeDestroy(mNativeStream, sendOnCanceled); |
+ mNativeStream = 0; |
+ mRequestContext.onRequestDestroyed(); |
+ if (mOnDestroyedCallbackForTesting != null) { |
+ mOnDestroyedCallbackForTesting.run(); |
+ } |
+ } |
+ |
+ /** |
+ * Fails the stream with an exception. Only called on the Executor. |
+ */ |
+ private void failWithExceptionOnExecutor(CronetException e) { |
+ // Do not call into listener if request is complete. |
+ synchronized (mNativeStreamLock) { |
+ if (isDoneLocked()) { |
+ return; |
+ } |
+ mReadState = mWriteState = State.ERROR; |
+ destroyNativeStreamLocked(false); |
+ } |
+ try { |
+ mCallback.onFailed(this, mResponseInfo, e); |
+ } catch (Exception failException) { |
+ Log.e(CronetUrlRequestContext.LOG_TAG, "Exception notifying of failed request", |
+ failException); |
+ } |
+ } |
+ |
+ /** |
+ * If callback method throws an exception, stream gets canceled |
+ * and exception is reported via onFailed callback. |
+ * Only called on the Executor. |
+ */ |
+ private void onCallbackException(Exception e) { |
+ CronetException streamError = |
+ new CronetException("CalledByNative method has thrown an exception", e); |
+ Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in CalledByNative method", e); |
+ failWithExceptionOnExecutor(streamError); |
+ } |
+ |
+ /** |
+ * Fails the stream with an exception. Can be called on any thread. |
+ */ |
+ private void failWithException(final CronetException exception) { |
+ postTaskToExecutor(new Runnable() { |
+ public void run() { |
+ failWithExceptionOnExecutor(exception); |
+ } |
+ }); |
+ } |
+ |
+ // Native methods are implemented in cronet_bidirectional_stream_adapter.cc. |
+ private native long nativeCreateBidirectionalStream(long urlRequestContextAdapter); |
+ |
+ @NativeClassQualifiedName("CronetBidirectionalStreamAdapter") |
+ private native int nativeStart(long nativePtr, String url, int priority, String method, |
+ String[] headers, boolean endOfStream); |
+ |
+ @NativeClassQualifiedName("CronetBidirectionalStreamAdapter") |
+ private native boolean nativeReadData( |
+ long nativePtr, ByteBuffer byteBuffer, int position, int limit); |
+ |
+ @NativeClassQualifiedName("CronetBidirectionalStreamAdapter") |
+ private native boolean nativeWriteData( |
+ long nativePtr, ByteBuffer byteBuffer, int position, int limit, boolean endOfStream); |
+ |
+ @NativeClassQualifiedName("CronetBidirectionalStreamAdapter") |
+ private native void nativeDestroy(long nativePtr, boolean sendOnCanceled); |
+} |