-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable the "received byte count" feature. #2004
Changes from 13 commits
f396a9b
28dd848
c57702e
5087b6b
277eb61
9fadc39
f1aef3c
181b155
4c8e4a7
e854aa4
38c956e
aa64e81
cd95929
b75695c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
import java.util.AbstractMap; | ||
import java.util.AbstractMap.SimpleEntry; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
|
@@ -29,6 +30,7 @@ | |
import org.chromium.net.CallbackException; | ||
import org.chromium.net.CronetException; | ||
import org.chromium.net.InlineExecutionProhibitedException; | ||
import org.chromium.net.RequestFinishedInfo; | ||
import org.chromium.net.UploadDataProvider; | ||
|
||
/** UrlRequest, backed by Envoy-Mobile. */ | ||
|
@@ -88,7 +90,7 @@ public final class CronetUrlRequest extends UrlRequestBase { | |
|
||
private final String mUserAgent; | ||
private final HeadersList mRequestHeaders = new HeadersList(); | ||
private final List<String> mUrlChain = new ArrayList<>(); | ||
private final Collection<Object> mRequestAnnotations; | ||
private final CronetUrlRequestContext mRequestContext; | ||
private final AtomicBoolean mWaitingOnRedirect = new AtomicBoolean(false); | ||
private final AtomicBoolean mWaitingOnRead = new AtomicBoolean(false); | ||
|
@@ -111,11 +113,16 @@ public final class CronetUrlRequest extends UrlRequestBase { | |
|
||
/* These don't change with redirects */ | ||
private String mInitialMethod; | ||
private CronetUploadDataStream mUploadDataStream; | ||
private final Executor mUserExecutor; | ||
private final VersionSafeCallbacks.UrlRequestCallback mCallback; | ||
private final String mInitialUrl; | ||
private final VersionSafeCallbacks.RequestFinishedInfoListener mRequestFinishedListener; | ||
private final ConditionVariable mStartBlock = new ConditionVariable(); | ||
|
||
private CronetUploadDataStream mUploadDataStream; | ||
|
||
private volatile CronetException mException; | ||
|
||
/** | ||
* Holds a subset of StatusValues - {@link State#STARTED} can represent {@link | ||
* Status#SENDING_REQUEST} or {@link Status#WAITING_FOR_RESPONSE}. While the distinction isn't | ||
|
@@ -127,13 +134,16 @@ public final class CronetUrlRequest extends UrlRequestBase { | |
* only used with the STARTED state, so it's inconsequential. | ||
*/ | ||
@StatusValues private volatile int mAdditionalStatusDetails = Status.INVALID; | ||
private final AtomicReference<CronetException> mError = new AtomicReference<>(); | ||
|
||
/* These change with redirects. */ | ||
private final AtomicReference<EnvoyHTTPStream> mStream = new AtomicReference<>(); | ||
private final List<String> mUrlChain = new ArrayList<>(); | ||
private EnvoyFinalStreamIntel mEnvoyFinalStreamIntel; | ||
private long mBytesReceivedFromRedirects = 0; | ||
private long mBytesReceivedFromLastRedirect = 0; | ||
private CronvoyHttpCallbacks mCronvoyCallbacks; | ||
private String mCurrentUrl; | ||
private UrlResponseInfoImpl mUrlResponseInfo; | ||
private volatile UrlResponseInfoImpl mUrlResponseInfo; | ||
private String mPendingRedirectUrl; | ||
|
||
/** | ||
|
@@ -142,8 +152,9 @@ public final class CronetUrlRequest extends UrlRequestBase { | |
*/ | ||
CronetUrlRequest(CronetUrlRequestContext cronvoyEngine, Callback callback, Executor executor, | ||
String url, String userAgent, boolean allowDirectExecutor, | ||
boolean trafficStatsTagSet, int trafficStatsTag, boolean trafficStatsUidSet, | ||
int trafficStatsUid) { | ||
Collection<Object> connectionAnnotations, boolean trafficStatsTagSet, | ||
int trafficStatsTag, boolean trafficStatsUidSet, int trafficStatsUid, | ||
RequestFinishedInfo.Listener requestFinishedListener) { | ||
if (url == null) { | ||
throw new NullPointerException("URL is required"); | ||
} | ||
|
@@ -154,11 +165,17 @@ public final class CronetUrlRequest extends UrlRequestBase { | |
throw new NullPointerException("Executor is required"); | ||
} | ||
mCallback = new VersionSafeCallbacks.UrlRequestCallback(callback); | ||
mRequestFinishedListener = | ||
requestFinishedListener != null | ||
? new VersionSafeCallbacks.RequestFinishedInfoListener(requestFinishedListener) | ||
: null; | ||
mRequestContext = cronvoyEngine; | ||
mAllowDirectExecutor = allowDirectExecutor; | ||
mUserExecutor = executor; | ||
mInitialUrl = url; | ||
mCurrentUrl = url; | ||
mUserAgent = userAgent; | ||
mRequestAnnotations = connectionAnnotations; | ||
} | ||
|
||
@Override | ||
|
@@ -305,8 +322,8 @@ public boolean isDone() { | |
|
||
@Override | ||
public void getStatus(StatusListener listener) { | ||
@StatusValues int extraStatus = mAdditionalStatusDetails; | ||
@State int state = mState.get(); | ||
int extraStatus = mAdditionalStatusDetails; | ||
|
||
@StatusValues final int status; | ||
switch (state) { | ||
|
@@ -401,7 +418,6 @@ private static int determineNextErrorState(boolean streamEnded, @State int origi | |
} | ||
|
||
private void enterErrorState(CronetException error) { | ||
mError.compareAndSet(null, error); | ||
@State int originalState; | ||
@State int updatedState; | ||
do { | ||
|
@@ -414,6 +430,7 @@ private void enterErrorState(CronetException error) { | |
if (isTerminalState(originalState)) { | ||
return; | ||
} | ||
mException = error; | ||
fireCloseUploadDataProvider(); | ||
if (updatedState == State.ERROR_PENDING_CANCEL) { | ||
CronvoyHttpCallbacks cronvoyCallbacks = this.mCronvoyCallbacks; | ||
|
@@ -469,12 +486,17 @@ private void fireCloseUploadDataProvider() { | |
} | ||
} | ||
|
||
// This method is only called when in STARTED state. This means a "cancel" request won't be | ||
// executed immediately - that quite important here, otherwise this would lead to unfortunate | ||
// race conditions. A "cancel" request will then be honnored on the first callback. | ||
private void fireOpenConnection() { | ||
if (mInitialMethod == null) { | ||
mInitialMethod = "GET"; | ||
} | ||
mUrlResponseInfo = null; | ||
mEnvoyFinalStreamIntel = null; | ||
mBytesReceivedFromRedirects += mBytesReceivedFromLastRedirect; | ||
mAdditionalStatusDetails = Status.CONNECTING; | ||
mUrlResponseInfo = new UrlResponseInfoImpl(); | ||
mUrlChain.add(mCurrentUrl); | ||
Map<String, List<String>> envoyRequestHeaders = | ||
buildEnvoyRequestHeaders(mInitialMethod, mRequestHeaders, mUploadDataStream, mUserAgent, | ||
|
@@ -568,7 +590,7 @@ public void run() { | |
try { | ||
mCallback.onCanceled(CronetUrlRequest.this, mUrlResponseInfo); | ||
} catch (Exception exception) { | ||
Log.e(TAG, "Exception in onCanceled method", exception); | ||
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onCanceled method", exception); | ||
} | ||
} | ||
}; | ||
|
@@ -582,7 +604,7 @@ public void run() { | |
try { | ||
mCallback.onSucceeded(CronetUrlRequest.this, mUrlResponseInfo); | ||
} catch (Exception exception) { | ||
Log.e(TAG, "Exception in onSucceeded method", exception); | ||
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onSucceeded method", exception); | ||
} | ||
} | ||
}; | ||
|
@@ -594,9 +616,9 @@ void onFailed() { | |
@Override | ||
public void run() { | ||
try { | ||
mCallback.onFailed(CronetUrlRequest.this, mUrlResponseInfo, mError.get()); | ||
mCallback.onFailed(CronetUrlRequest.this, mUrlResponseInfo, mException); | ||
} catch (Exception exception) { | ||
Log.e(TAG, "Exception in onFailed method", exception); | ||
Log.e(CronetUrlRequestContext.LOG_TAG, "Exception in onFailed method", exception); | ||
} | ||
} | ||
}; | ||
|
@@ -631,6 +653,19 @@ private boolean streamEnded() { | |
return cronvoyCallbacks != null && cronvoyCallbacks.mEndStream; | ||
} | ||
|
||
private void recordEnvoyFinalStreamIntel(EnvoyFinalStreamIntel envoyFinalStreamIntel) { | ||
mEnvoyFinalStreamIntel = envoyFinalStreamIntel; | ||
if (mUrlResponseInfo != null) { // Null if cancelled before receiving a Response. | ||
mUrlResponseInfo.setReceivedByteCount(envoyFinalStreamIntel.getReceivedByteCount() + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so I thought cronvoy had the following behavior There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, but if java gets the headers, and say half the body bytes which were read off the wire, what would cronet expect to report for bytes read: bytes read off the wire, or bytes handed up to java? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still curious about this one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good question. There are no tests for that. Here is what I found after many hours of "cout": Cronet is not consistent when reporting the received byte count. There is one test with an OK response where all the bytes are counted, i.e. == I "cout"ed the Buffer in codec_impl.cc This is the content of the test with "an OK response":
The size is perfectly matching the value in the original Cronet test. This is the content of the test with a "NOT FOUND":
But this one the original Cronet test expects 120 bytes: this looks like the size without the Status Line. I'm not yet sure how to reconcile this. |
||
mBytesReceivedFromRedirects); | ||
} | ||
} | ||
|
||
private void recordEnvoyStreamIntel(EnvoyStreamIntel envoyStreamIntel) { | ||
mUrlResponseInfo.setReceivedByteCount(envoyStreamIntel.getReceivedByteCount() + | ||
mBytesReceivedFromRedirects); | ||
} | ||
|
||
private static class HeadersList extends ArrayList<Map.Entry<String, String>> {} | ||
|
||
private static class DirectExecutor implements Executor { | ||
|
@@ -666,9 +701,8 @@ public Executor getExecutor() { | |
@Override | ||
public void onHeaders(Map<String, List<String>> headers, boolean endStream, | ||
EnvoyStreamIntel streamIntel) { | ||
if (isAbandoned()) { | ||
return; | ||
} | ||
mUrlResponseInfo = new UrlResponseInfoImpl(); | ||
recordEnvoyStreamIntel(streamIntel); | ||
mEndStream = endStream; | ||
List<String> statuses = headers.get(":status"); | ||
final int responseCode = | ||
|
@@ -698,6 +732,7 @@ public void onHeaders(Map<String, List<String>> headers, boolean endStream, | |
} | ||
|
||
if (locationField != null) { | ||
mBytesReceivedFromLastRedirect = streamIntel.getReceivedByteCount(); | ||
cancel(); // Abort the the original request - we are being redirected. | ||
} | ||
|
||
|
@@ -734,6 +769,7 @@ public void onData(ByteBuffer data, boolean endStream, EnvoyStreamIntel streamIn | |
if (isAbandoned()) { | ||
return; | ||
} | ||
recordEnvoyStreamIntel(streamIntel); | ||
mEndStream = endStream; | ||
@State int originalState; | ||
@State int updatedState; | ||
|
@@ -789,6 +825,7 @@ public void onError(int errorCode, String message, int attemptCount, | |
if (isAbandoned()) { | ||
return; | ||
} | ||
recordEnvoyFinalStreamIntel(finalStreamIntel); | ||
mEndStream = true; | ||
@State int originalState; | ||
@State int updatedState; | ||
|
@@ -811,6 +848,7 @@ public void onCancel(EnvoyStreamIntel streamIntel, EnvoyFinalStreamIntel finalSt | |
if (isAbandoned()) { | ||
return; | ||
} | ||
recordEnvoyFinalStreamIntel(finalStreamIntel); | ||
mEndStream = true; | ||
@State int originalState; | ||
@State int updatedState; | ||
|
@@ -852,7 +890,7 @@ public void onComplete(EnvoyStreamIntel streamIntel, EnvoyFinalStreamIntel final | |
if (isAbandoned()) { | ||
return; | ||
} | ||
mUrlResponseInfo.setReceivedByteCount(finalStreamIntel.getSentByteCount()); | ||
recordEnvoyFinalStreamIntel(finalStreamIntel); | ||
if (successReady(SucceededState.ON_COMPLETE_RECEIVED)) { | ||
onSucceeded(); | ||
} | ||
|
@@ -900,7 +938,7 @@ void readData(int size) { | |
*/ | ||
void cancel() { | ||
EnvoyHTTPStream stream = mStream.get(); | ||
if (isAbandoned() || mEndStream) { | ||
if (this != mCronvoyCallbacks || mEndStream) { | ||
return; | ||
} | ||
@CancelState int oldState = mCancelState.getAndSet(CancelState.CANCELLED); | ||
|
@@ -936,6 +974,7 @@ private void setUrlResponseInfo(Map<String, List<String>> responseHeaders, int r | |
// TODO(https://github.com/envoyproxy/envoy-mobile/issues/1426) set receivedByteCount | ||
// TODO(https://github.com/envoyproxy/envoy-mobile/issues/1622) support proxy | ||
// TODO(https://github.com/envoyproxy/envoy-mobile/issues/1546) negotiated protocol | ||
// TODO(https://github.com/envoyproxy/envoy-mobile/issues/1578) http caching | ||
mUrlResponseInfo.setResponseValues( | ||
new ArrayList<>(mUrlChain), responseCode, HttpReason.getReason(responseCode), | ||
Collections.unmodifiableList(headerList), false, selectedTransport, ":0"); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update commments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments updated, and also using the same names everywhere. Thanks.