Skip to content

Commit

Permalink
Add selective interception. (#279)
Browse files Browse the repository at this point in the history
* Add selective interception.
* Update README.md.
* Align formatting in README with other points.
* Avoid header name duplication.
* Strip interception header also in the no-op library.
  • Loading branch information
MiSikora authored Mar 19, 2020
1 parent 244d3da commit d14ed64
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 11 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ try {

### Redact-Header 👮‍♂️

**Warning** The data generated and stored when using Chucker may contain sensitive information such as Authorization or Cookie headers, and the contents of request and response bodies.
**Warning** The data generated and stored when using Chucker may contain sensitive information such as Authorization or Cookie headers, and the contents of request and response bodies.

It is intended for **use during development**, and not in release builds or other production deployments.

Expand All @@ -129,6 +129,30 @@ You can redact headers that contain sensitive information by calling `redactHead
interceptor.redactHeader("Auth-Token", "User-Session");
```

### Skip-Inspection ️🕵️

If you need to selectively skip Chucker inspection on some endpoints or on particular requests you can add a special header - `Skip-ChuckerInterceptor: true`. This will inform Chucker to not process this request. Chucker will also strip this header from any request before sending it to a server.

If you use `OkHttp` directly, create requests like below.

```kotlin
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "true")
.build()

client.newCall(request).execute()
```

If you are a `Retrofit` user you can configure it per endpoint like this.

```kotlin
fun Service {
@GET("/")
@Headers(Chucker.SKIP_INTERCEPTOR_HEADER)
fun networkRequest(): Unit
}
```

## Migrating 🚗

If you're migrating **from [Chuck](https://github.com/jgilfelt/chuck) to Chucker**, please refer to this [migration guide](/docs/migrating-from-chuck.md).
Expand Down
7 changes: 7 additions & 0 deletions library-no-op/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ artifacts {
dependencies {
implementation "com.squareup.okhttp3:okhttp:$okhttp3Version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testImplementation "io.mockk:mockk:$mockkVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp3Version"
testImplementation "com.google.truth:truth:$truthVersion"
}

apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ object Chucker {
const val SCREEN_HTTP = 1
const val SCREEN_ERROR = 2

const val SKIP_INTERCEPTOR_HEADER_NAME = "Skip-Chucker-Interceptor"
const val SKIP_INTERCEPTOR_HEADER = "$SKIP_INTERCEPTOR_HEADER_NAME: true"

@Suppress("MayBeConst ") // https://github.com/ChuckerTeam/chucker/pull/169#discussion_r362341353
val isOp = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ class ChuckerInterceptor @JvmOverloads constructor(

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val request = chain.request().newBuilder()
.removeHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME)
.build()
return chain.proceed(request)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.chuckerteam.chucker.api

import android.content.Context
import com.google.common.truth.Truth.assertThat
import io.mockk.every
import io.mockk.mockk
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.jupiter.api.Test

class ChuckerInterceptorTest {
@get:Rule val server = MockWebServer()
private val serverUrl = server.url("/") // Starts server implicitly
private val mockContext = mockk<Context> {
every { getString(any()) } returns ""
}
private val client = OkHttpClient.Builder()
.addInterceptor(ChuckerInterceptor(mockContext))
.build()

@Test
fun skipChuckerHeader_isNotAvailableForTheServerRequest() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "true")
.build()

val response = client.newCall(request).execute()

assertThat(response.request().header(Chucker.SKIP_INTERCEPTOR_HEADER_NAME)).isNull()
}

@Test
fun doNotSkipChuckerHeader_isNotAvailableForTheServerRequest() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "false")
.build()

val response = client.newCall(request).execute()

assertThat(response.request().header(Chucker.SKIP_INTERCEPTOR_HEADER_NAME)).isNull()
}
}
3 changes: 3 additions & 0 deletions library/src/main/java/com/chuckerteam/chucker/api/Chucker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ object Chucker {
const val SCREEN_HTTP = 1
const val SCREEN_ERROR = 2

const val SKIP_INTERCEPTOR_HEADER_NAME = "Skip-Chucker-Interceptor"
const val SKIP_INTERCEPTOR_HEADER = "$SKIP_INTERCEPTOR_HEADER_NAME: true"

/**
* Check if this instance is the operation one or no-op.
* @return `true` if this is the operation instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ class ChuckerInterceptor @JvmOverloads constructor(

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val request = chain.request().newBuilder()
.removeHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME)
.build()

if (chain.request().header(Chucker.SKIP_INTERCEPTOR_HEADER_NAME)?.toBoolean() == true) {
return chain.proceed(request)
}

val response: Response
val transaction = HttpTransaction()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package com.chuckerteam.chucker.api
import android.content.Context
import com.chuckerteam.chucker.getResourceFile
import com.chuckerteam.chucker.internal.data.entity.HttpTransaction
import com.google.common.truth.Truth.assertThat
import io.mockk.every
import io.mockk.mockk
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertTrue
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.mockwebserver.MockResponse
Expand All @@ -15,7 +14,7 @@ import okio.Buffer
import okio.ByteString
import okio.GzipSink
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.Test

class ChuckerInterceptorTest {
@get:Rule val server = MockWebServer()
Expand Down Expand Up @@ -45,7 +44,7 @@ class ChuckerInterceptorTest {

client.newCall(request).execute()

assertEquals(expectedBody, ByteString.of(*transaction!!.responseImageData!!))
assertThat(expectedBody).isEqualTo(ByteString.of(*transaction!!.responseImageData!!))
}

@Test
Expand All @@ -57,7 +56,7 @@ class ChuckerInterceptorTest {

val responseBody = client.newCall(request).execute().body()!!.source().readByteString()

assertEquals(expectedBody, responseBody)
assertThat(expectedBody).isEqualTo(responseBody)
}

@Test
Expand All @@ -71,8 +70,8 @@ class ChuckerInterceptorTest {

client.newCall(request).execute()

assertTrue(transaction!!.isResponseBodyPlainText)
assertEquals("Hello, world!", transaction!!.responseBody)
assertThat(transaction!!.isResponseBodyPlainText).isTrue()
assertThat(transaction!!.responseBody).isEqualTo("Hello, world!")
}

@Test
Expand All @@ -86,6 +85,78 @@ class ChuckerInterceptorTest {

val responseBody = client.newCall(request).execute().body()!!.source().readByteString()

assertEquals("Hello, world!", responseBody.utf8())
assertThat(responseBody.utf8()).isEqualTo("Hello, world!")
}

@Test
fun requestThatShouldBeSkipped_isNotProcessedByChucker() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "true")
.build()

client.newCall(request).execute()

assertThat(transaction).isNull()
}

@Test
fun requestThatShouldBeSkipped_isDeliveredToTheEndConsumer() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "true")
.build()

val body = client.newCall(request).execute().body()!!.string()

assertThat(body).isEqualTo("Hello, world!")
}

@Test
fun requestThatShouldNotBeSkipped_isProcessedByChucker() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "false")
.build()

client.newCall(request).execute()

assertThat(transaction!!.responseBody).isEqualTo("Hello, world!")
}

@Test
fun requestThatShouldNotBeSkipped_isDeliveredToTheEndConsumer() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "false")
.build()

val body = client.newCall(request).execute().body()!!.string()

assertThat(body).isEqualTo("Hello, world!")
}

@Test
fun skipChuckerHeader_isNotAvailableForTheServerRequest() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "true")
.build()

val response = client.newCall(request).execute()

assertThat(response.request().header(Chucker.SKIP_INTERCEPTOR_HEADER_NAME)).isNull()
}

@Test
fun doNotSkipChuckerHeader_isNotAvailableForTheServerRequest() {
server.enqueue(MockResponse().setBody("Hello, world!"))
val request = Request.Builder().url(serverUrl)
.addHeader(Chucker.SKIP_INTERCEPTOR_HEADER_NAME, "false")
.build()

val response = client.newCall(request).execute()

assertThat(response.request().header(Chucker.SKIP_INTERCEPTOR_HEADER_NAME)).isNull()
}
}

0 comments on commit d14ed64

Please sign in to comment.