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

Only rebuild response in OkHttp interceptor if we modify the body #33

Merged
merged 1 commit into from
Oct 30, 2023
Merged
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 @@ -40,81 +40,69 @@ public class EmbraceOkHttp3NetworkInterceptor internal constructor(
) : Interceptor {
public constructor() : this(Embrace.getInstance(), Clock { System.currentTimeMillis() })

@Suppress("ComplexMethod")
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// If the SDK has not started, don't do anything
val originalRequest: Request = chain.request()
if (!embrace.isStarted || embrace.internalInterface.isInternalNetworkCaptureDisabled()) {
return chain.proceed(originalRequest)
}
val offset = sdkClockOffset()

val networkSpanForwardingEnabled = embrace.internalInterface.isNetworkSpanForwardingEnabled()
var traceparent: String? = null
if (networkSpanForwardingEnabled && originalRequest.header(TRACEPARENT_HEADER_NAME) == null) {
traceparent = embrace.generateW3cTraceparent()
}
val request =
if (traceparent == null) originalRequest else originalRequest.newBuilder().header(TRACEPARENT_HEADER_NAME, traceparent).build()

// Take a snapshot of the difference in the system and SDK clocks and send the request along the chain
val offset = sdkClockOffset()
val networkResponse: Response = chain.proceed(request)
val responseBuilder: Response.Builder = networkResponse.newBuilder().request(request)
var contentLength: Long? = null
// Try to get the content length from the header
val contentLengthHeaderValue = networkResponse.header(CONTENT_LENGTH_HEADER_NAME)
if (contentLengthHeaderValue != null) {
try {
contentLength = contentLengthHeaderValue.toLong()
} catch (ex: Exception) {
// Ignore
}
}

// If we get the body for a server-sent events stream, then we will wait forever
val contentType = networkResponse.header(CONTENT_TYPE_HEADER_NAME)
// Get response and determine the size of the body
var contentLength: Long? = getContentLengthFromHeader(networkResponse)

// Tolerant of a charset specified in header,
// e.g. Content-Type: text/event-stream;charset=UTF-8
val serverSentEvent = contentType != null && contentType.startsWith(CONTENT_TYPE_EVENT_STREAM)
if (!serverSentEvent && contentLength == null) {
try {
val body = networkResponse.body
if (body != null) {
val source = body.source()
source.request(Long.MAX_VALUE)
contentLength = source.buffer.size
}
} catch (ex: Exception) {
// Ignore
}
if (contentLength == null) {
// If we get the body for a server-sent events stream, then we will wait forever
contentLength = getContentLengthFromBody(networkResponse, networkResponse.header(CONTENT_TYPE_HEADER_NAME))
}

if (contentLength == null) {
// Otherwise default to zero
// Set the content length to 0 if we can't determine it
contentLength = 0L
}
val shouldCaptureNetworkData = embrace.internalInterface.shouldCaptureNetworkBody(request.url.toString(), request.method)
if (shouldCaptureNetworkData &&
ENCODING_GZIP.equals(networkResponse.header(CONTENT_ENCODING_HEADER_NAME), ignoreCase = true) &&
networkResponse.promisesBody()
) {
val body = networkResponse.body
if (body != null) {
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll(CONTENT_ENCODING_HEADER_NAME)
.removeAll(CONTENT_LENGTH_HEADER_NAME)
.build()
val realResponseBody = RealResponseBody(
contentType,
-1L,
GzipSource(body.source()).buffer()
)
responseBuilder.headers(strippedHeaders)
responseBuilder.body(realResponseBody)
}
}
val response: Response = responseBuilder.build()

var response: Response = networkResponse
var networkCaptureData: NetworkCaptureData? = null
val shouldCaptureNetworkData = embrace.internalInterface.shouldCaptureNetworkBody(request.url.toString(), request.method)

// If we need to capture the network response body,
if (shouldCaptureNetworkData) {
if (ENCODING_GZIP.equals(networkResponse.header(CONTENT_ENCODING_HEADER_NAME), ignoreCase = true) &&
networkResponse.promisesBody()
) {
val body = networkResponse.body
if (body != null) {
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll(CONTENT_ENCODING_HEADER_NAME)
.removeAll(CONTENT_LENGTH_HEADER_NAME)
.build()
val realResponseBody = RealResponseBody(
networkResponse.header(CONTENT_TYPE_HEADER_NAME),
-1L,
GzipSource(body.source()).buffer()
)
val responseBuilder = networkResponse.newBuilder().request(request)
responseBuilder.headers(strippedHeaders)
responseBuilder.body(realResponseBody)
response = responseBuilder.build()
}
}

networkCaptureData = getNetworkCaptureData(request, response)
}

embrace.recordNetworkRequest(
EmbraceNetworkRequest.fromCompletedRequest(
EmbraceHttpPathOverride.getURLString(EmbraceOkHttp3PathOverrideRequest(request)),
Expand All @@ -132,6 +120,40 @@ public class EmbraceOkHttp3NetworkInterceptor internal constructor(
return response
}

private fun getContentLengthFromHeader(networkResponse: Response): Long? {
Copy link
Collaborator Author

@bidetofevil bidetofevil Oct 29, 2023

Choose a reason for hiding this comment

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

These methods were pulled out of intercept to make it easier to read. No change logic was made along with the move other than contentType now being read form the headers as needed rather the always read and stored in a variable (it is probably never actually read twice anyway since most responses come with a content-length header and we don't typically need to grab it from the body)

var contentLength: Long? = null
val contentLengthHeaderValue = networkResponse.header(CONTENT_LENGTH_HEADER_NAME)
if (contentLengthHeaderValue != null) {
try {
contentLength = contentLengthHeaderValue.toLong()
} catch (ex: Exception) {
// Ignore
}
}
return contentLength
}

private fun getContentLengthFromBody(networkResponse: Response, contentType: String?): Long? {
var contentLength: Long? = null

// Tolerant of a charset specified in header, e.g. Content-Type: text/event-stream;charset=UTF-8
val serverSentEvent = contentType != null && contentType.startsWith(CONTENT_TYPE_EVENT_STREAM)
if (!serverSentEvent) {
try {
val body = networkResponse.body
if (body != null) {
val source = body.source()
source.request(Long.MAX_VALUE)
contentLength = source.buffer.size
}
} catch (ex: Exception) {
// Ignore
}
}

return contentLength
}

private fun getNetworkCaptureData(request: Request, response: Response): NetworkCaptureData {
var requestHeaders: Map<String, String>? = null
var requestQueryParams: String? = null
Expand Down
Loading