Skip to content
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

Additional EmbraceUrlConnectionDelegate unit tests and bug fixes #50

Merged
merged 4 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ interface EmbraceHttpUrlConnection {
@Nullable
InputStream getErrorStream();

boolean shouldInterceptHeaderRetrieval(@Nullable String key);

@Nullable
String getHeaderField(int n);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public String getHeaderByName(@NonNull String name) {
public String getOverriddenURL(@NonNull String pathOverride) {
try {
return new URL(connection.getURL().getProtocol(), connection.getURL().getHost(),
connection.getURL().getPort(), pathOverride).toString();
connection.getURL().getPort(), pathOverride + "?" + connection.getURL().getQuery()).toString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this change? Is it because we were discarding the query params?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. This is the bug I mention in the second bullet point in the PR description. When you toString() a URL, it includes the query path as part of the returned string. When we return teh URL with an overridden path, we should do the same. (This is what the OkHttp implementation does)

} catch (MalformedURLException e) {
InternalStaticEmbraceLogger.logError("Failed to override path of " +
connection.getURL() + " with " + pathOverride);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@
/**
* The content encoding HTTP header.
*/
private static final String CONTENT_ENCODING = "Content-Encoding";
static final String CONTENT_ENCODING = "Content-Encoding";

/**
* The content length HTTP header.
*/
private static final String CONTENT_LENGTH = "Content-Length";
static final String CONTENT_LENGTH = "Content-Length";

/**
* Reference to the wrapped connection.
Expand Down Expand Up @@ -140,8 +140,7 @@
@Nullable
private volatile String traceparent = null;

@Nullable
private volatile byte[] responseBody = null;
private final boolean isSDKStarted;

/**
* Wraps an existing {@link HttpURLConnection} with the Embrace network logic.
Expand All @@ -160,6 +159,7 @@
this.embrace = embrace;
this.createdTime = embrace.getInternalInterface().getSdkCurrentTime();
this.callId = UUID.randomUUID().toString();
this.isSDKStarted = embrace.isStarted();
}

@Override
Expand All @@ -169,21 +169,23 @@

@Override
public void connect() throws IOException {
identifyTraceId();
try {
if (embrace.getInternalInterface().isNetworkSpanForwardingEnabled()) {
traceparent = connection.getRequestProperty(TRACEPARENT_HEADER_NAME);
if (isSDKStarted) {
identifyTraceId();
try {
if (embrace.getInternalInterface().isNetworkSpanForwardingEnabled()) {
traceparent = connection.getRequestProperty(TRACEPARENT_HEADER_NAME);
}
} catch (Exception e) {

Check warning on line 178 in embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/network/http/EmbraceUrlConnectionDelegate.java

View check run for this annotation

Codecov / codecov/patch

embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/network/http/EmbraceUrlConnectionDelegate.java#L178

Added line #L178 was not covered by tests
// Ignore traceparent if there was a problem obtaining it
}
} catch (Exception e) {
// Ignore traceparent if there was a problem obtaining it
}
this.connection.connect();
}

@Override
public void disconnect() {
// The network call must be logged before we close the transport
internalLogNetworkCall(this.createdTime);
internalLogNetworkCall(createdTime);
this.connection.disconnect();
}

Expand Down Expand Up @@ -287,8 +289,7 @@
return getWrappedInputStream(this.connection.getErrorStream());
}

@Override
public boolean shouldInterceptHeaderRetrieval(@Nullable String key) {
private boolean shouldInterceptHeaderRetrieval(@Nullable String key) {
return shouldUncompressGzip() && key != null && (key.equalsIgnoreCase(CONTENT_ENCODING) || key.equalsIgnoreCase(CONTENT_LENGTH));
}

Expand Down Expand Up @@ -358,7 +359,7 @@
@Nullable
public Map<String, List<String>> getHeaderFields() {
final long startTime = embrace.getInternalInterface().getSdkCurrentTime();
cacheResponseData();
cacheNetworkCallData();
internalLogNetworkCall(startTime);
return headerFields.get();
}
Expand All @@ -377,7 +378,7 @@
}

R result = action.invoke();
cacheResponseData();
cacheNetworkCallData();
internalLogNetworkCall(startTime);
return result;
}
Expand Down Expand Up @@ -473,7 +474,7 @@
public int getResponseCode() {
identifyTraceId();
long startTime = embrace.getInternalInterface().getSdkCurrentTime();
cacheResponseData();
cacheNetworkCallData();
internalLogNetworkCall(startTime);
return responseCode.get();
}
Expand All @@ -484,7 +485,7 @@
identifyTraceId();
long startTime = embrace.getInternalInterface().getSdkCurrentTime();
String responseMsg = this.connection.getResponseMessage();
cacheResponseData();
cacheNetworkCallData();
internalLogNetworkCall(startTime);
return responseMsg;
}
Expand Down Expand Up @@ -547,7 +548,9 @@
* ignored.
*/
synchronized void internalLogNetworkCall(long startTime) {
internalLogNetworkCall(startTime, embrace.getInternalInterface().getSdkCurrentTime(), false, null);
if (isSDKStarted) {
internalLogNetworkCall(startTime, embrace.getInternalInterface().getSdkCurrentTime(), false, null);
}
}

/**
Expand Down Expand Up @@ -658,8 +661,7 @@
hasNetworkCaptureRules(),
(bytesCount, responseBody) -> {
if (this.startTime != null && this.endTime != null) {
this.responseBody = responseBody;
cacheResponseData();
cacheNetworkCallData(responseBody);
internalLogNetworkCall(
this.startTime,
this.endTime,
Expand Down Expand Up @@ -695,7 +697,7 @@
}

private void identifyTraceId() {
if (traceId == null) {
if (isSDKStarted && traceId == null) {
try {
traceId = getRequestProperty(embrace.getTraceIdHeader());
} catch (Exception e) {
Expand Down Expand Up @@ -808,13 +810,13 @@
countingInputStream(new BufferedInputStream(connectionInputStream)) : connectionInputStream;
}

cacheResponseData();
cacheNetworkCallData();
internalLogNetworkCall(startTime);
return in;
}

private boolean hasNetworkCaptureRules() {
if (this.connection.getURL() == null) {
if (!isSDKStarted || this.connection.getURL() == null) {
return false;
}
String url = this.connection.getURL().toString();
Expand All @@ -823,11 +825,17 @@
return embrace.getInternalInterface().shouldCaptureNetworkBody(url, method);
}

private void cacheNetworkCallData() {
if (isSDKStarted) {
cacheNetworkCallData(null);
}
}

/**
* Cache values from response at the first point of availability so that we won't try to retrieve these values when the response
* is not available.
*/
private void cacheResponseData() {
private void cacheNetworkCallData(@Nullable byte[] responseBody) {
if (headerFields.get() == null) {
synchronized (headerFields) {
if (headerFields.get() == null) {
Expand Down Expand Up @@ -874,26 +882,42 @@
}
}

if (shouldCaptureNetworkData() && networkCaptureData.get() == null) {
if (shouldCaptureNetworkData()) {
// If we don't have network capture rules, it's unnecessary to save these values
synchronized (networkCaptureData) {
if (shouldCaptureNetworkData() && networkCaptureData.get() == null) {
if (shouldCaptureNetworkData()) {
try {
Map<String, String> requestHeaders = this.requestHeaders;
String requestQueryParams = connection.getURL().getQuery();
byte[] requestBody = this.outputStream != null ? this.outputStream.getRequestBody() : null;
Map<String, String> responseHeaders = getProcessedHeaders(headerFields.get());

networkCaptureData.set(
new NetworkCaptureData(
requestHeaders,
requestQueryParams,
requestBody,
responseHeaders,
responseBody,
null
)
);
NetworkCaptureData existingData = networkCaptureData.get();
if (existingData == null) {
Map<String, String> requestHeaders = this.requestHeaders;
String requestQueryParams = connection.getURL().getQuery();
byte[] requestBody = this.outputStream != null ? this.outputStream.getRequestBody() : null;
Map<String, String> responseHeaders = getProcessedHeaders(headerFields.get());

networkCaptureData.set(
new NetworkCaptureData(
requestHeaders,
requestQueryParams,
requestBody,
responseHeaders,
responseBody,
null
)
);
} else if (responseBody != null) {
// Update the response body field in the cached networkCaptureData object if a subsequent call
// is update to update the network logging with this data.
networkCaptureData.set(
new NetworkCaptureData(
existingData.getRequestHeaders(),
existingData.getRequestQueryParams(),
existingData.getCapturedRequestBody(),
existingData.getResponseHeaders(),
responseBody,
null
)
);
}
} catch (Exception e) {
lastConnectionAccessException = e;
}
Expand All @@ -903,6 +927,7 @@
}

private boolean shouldCaptureNetworkData() {
return hasNetworkCaptureRules() && (enableWrapIoStreams || inputStreamAccessException != null);
return (hasNetworkCaptureRules() && (enableWrapIoStreams || inputStreamAccessException != null)) &&
(networkCaptureData.get() == null || networkCaptureData.get().getCapturedResponseBody() == null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.embrace.android.embracesdk.internal.network.http

import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Test

internal class EmbraceHttpPathOverrideTest {

@Test
fun `check override path validity`() {
val request: HttpPathOverrideRequest = mockk(relaxed = true)
every { request.urlString } answers { defaultUrl }
every { request.getOverriddenURL(customPath) } answers { customUrl }
every { request.getOverriddenURL("/error") } answers { throw RuntimeException() }

assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, null))
assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, ""))
assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, "/a".repeat(1025)))
assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, "/屈福特"))
assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, "watford"))
assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, "/custom#"))
assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, ""))
assertEquals(defaultUrl, EmbraceHttpPathOverride.getURLString(request, "/error"))
assertEquals(customUrl, EmbraceHttpPathOverride.getURLString(request, customPath))
}

companion object {
private const val defaultUrl = "https://embrace.io/default-path"
private const val customPath = "/custom-path"
private const val customUrl = "https://embrace.io$customPath"
}
}
Loading