Skip to content

Commit

Permalink
Encode/Decode internal apollo3 headers (#2707)
Browse files Browse the repository at this point in the history
  • Loading branch information
lbloder authored May 19, 2023
1 parent 46dd45b commit b6b3a90
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- Ensure screenshots and view hierarchies are captured on the main thread ([#2712](https://github.com/getsentry/sentry-java/pull/2712))
- Base64 encode internal Apollo3 Headers ([#2707](https://github.com/getsentry/sentry-java/pull/2707))

## 6.19.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.sentry.SpanStatus
import io.sentry.TypeCheckHint
import io.sentry.util.PropagationTargetsUtils
import io.sentry.util.UrlUtils
import io.sentry.vendor.Base64

class SentryApollo3HttpInterceptor @JvmOverloads constructor(private val hub: IHub = HubAdapter.getInstance(), private val beforeSpan: BeforeSpanCallback? = null) :
HttpInterceptor, IntegrationName {
Expand Down Expand Up @@ -96,10 +97,11 @@ class SentryApollo3HttpInterceptor @JvmOverloads constructor(private val hub: IH
val method = request.method

val operationName = operationNameFromHeaders(request)
val operationType = request.valueForHeader(SENTRY_APOLLO_3_OPERATION_TYPE)
val operationType = decodeHeaderValue(request, SENTRY_APOLLO_3_OPERATION_TYPE)
val operation = if (operationType != null) "http.graphql.$operationType" else "http.graphql"
val operationId = request.valueForHeader("X-APOLLO-OPERATION-ID")
val variables = request.valueForHeader(SENTRY_APOLLO_3_VARIABLES)
val variables = decodeHeaderValue(request, SENTRY_APOLLO_3_VARIABLES)

val description = "${operationType ?: method} ${operationName ?: urlDetails.urlOrFallback}"

return activeSpan.startChild(operation, description).apply {
Expand All @@ -116,7 +118,19 @@ class SentryApollo3HttpInterceptor @JvmOverloads constructor(private val hub: IH
}

private fun operationNameFromHeaders(request: HttpRequest): String? {
return request.valueForHeader(SENTRY_APOLLO_3_OPERATION_NAME) ?: request.valueForHeader("X-APOLLO-OPERATION-NAME")
return decodeHeaderValue(request, SENTRY_APOLLO_3_OPERATION_NAME)
?: request.valueForHeader("X-APOLLO-OPERATION-NAME")
}

private fun decodeHeaderValue(request: HttpRequest, headerName: String): String? {
return request.valueForHeader(headerName)?.let {
try {
String(Base64.decode(it, Base64.DEFAULT))
} catch (e: IllegalArgumentException) {
hub.options.logger.log(SentryLevel.ERROR, "Error decoding internal apolloHeader $headerName", e)
return null
}
}
}

private fun HttpRequest.valueForHeader(key: String) = headers.firstOrNull { it.name == key }?.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import com.apollographql.apollo3.api.Subscription
import com.apollographql.apollo3.api.variables
import com.apollographql.apollo3.interceptor.ApolloInterceptor
import com.apollographql.apollo3.interceptor.ApolloInterceptorChain
import io.sentry.apollo3.SentryApollo3HttpInterceptor.Companion.SENTRY_APOLLO_3_OPERATION_NAME
import io.sentry.apollo3.SentryApollo3HttpInterceptor.Companion.SENTRY_APOLLO_3_OPERATION_TYPE
import io.sentry.apollo3.SentryApollo3HttpInterceptor.Companion.SENTRY_APOLLO_3_VARIABLES
import io.sentry.vendor.Base64
import kotlinx.coroutines.flow.Flow

class SentryApollo3Interceptor : ApolloInterceptor {
Expand All @@ -19,11 +23,11 @@ class SentryApollo3Interceptor : ApolloInterceptor {
chain: ApolloInterceptorChain
): Flow<ApolloResponse<D>> {
val builder = request.newBuilder()
.addHttpHeader(SentryApollo3HttpInterceptor.SENTRY_APOLLO_3_OPERATION_TYPE, operationType(request))
.addHttpHeader(SentryApollo3HttpInterceptor.SENTRY_APOLLO_3_OPERATION_NAME, request.operation.name())
.addHttpHeader(SENTRY_APOLLO_3_OPERATION_TYPE, Base64.encodeToString(operationType(request).toByteArray(), Base64.DEFAULT))
.addHttpHeader(SENTRY_APOLLO_3_OPERATION_NAME, Base64.encodeToString(request.operation.name().toByteArray(), Base64.DEFAULT))

request.scalarAdapters?.let {
builder.addHttpHeader(SentryApollo3HttpInterceptor.SENTRY_APOLLO_3_VARIABLES, request.operation.variables(it).valueMap.toString())
builder.addHttpHeader(SENTRY_APOLLO_3_VARIABLES, Base64.encodeToString(request.operation.variables(it).valueMap.toString().toByteArray(), Base64.DEFAULT))
}
return chain.proceed(builder.build())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ class SentryApollo3InterceptorTest {
}
}

private fun executeQuery(sut: ApolloClient = fixture.getSut(), isSpanActive: Boolean = true) = runBlocking {
private fun executeQuery(sut: ApolloClient = fixture.getSut(), isSpanActive: Boolean = true, id: String = "83") = runBlocking {
var tx: ITransaction? = null
if (isSpanActive) {
tx = SentryTracer(TransactionContext("op", "desc", TracesSamplingDecision(true)), fixture.hub)
Expand All @@ -274,7 +274,7 @@ class SentryApollo3InterceptorTest {

val coroutine = launch {
try {
sut.query(LaunchDetailsQuery("83")).execute()
sut.query(LaunchDetailsQuery(id)).execute()
} catch (e: ApolloException) {
return@launch
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ class SentryApollo3InterceptorWithVariablesTest {
)
}

@Test
fun `handles non-ascii header values correctly`() {
executeQuery(id = "á")

verify(fixture.hub).captureTransaction(
check {
assertTransactionDetails(it)
assertEquals(SpanStatus.OK, it.spans.first().status)
},
anyOrNull<TraceContext>(),
anyOrNull(),
anyOrNull()
)
}

@Test
fun `adds breadcrumb when http calls succeeds`() {
executeQuery(fixture.getSut())
Expand Down Expand Up @@ -153,7 +168,7 @@ class SentryApollo3InterceptorWithVariablesTest {
}
}

private fun executeQuery(sut: ApolloClient = fixture.getSut(), isSpanActive: Boolean = true) = runBlocking {
private fun executeQuery(sut: ApolloClient = fixture.getSut(), isSpanActive: Boolean = true, id: String = "83") = runBlocking {
var tx: ITransaction? = null
if (isSpanActive) {
tx = SentryTracer(TransactionContext("op", "desc", TracesSamplingDecision(true)), fixture.hub)
Expand All @@ -162,7 +177,7 @@ class SentryApollo3InterceptorWithVariablesTest {

val coroutine = launch {
try {
sut.query(LaunchDetailsQuery("83")).execute()
sut.query(LaunchDetailsQuery(id)).execute()
} catch (e: ApolloException) {
return@launch
}
Expand Down

0 comments on commit b6b3a90

Please sign in to comment.