Skip to content

feat: add gRPC interceptor support as library-grpc module#1631

Draft
MRezaNasirloo wants to merge 3 commits into
ChuckerTeam:mainfrom
MRezaNasirloo:feature/grpc-interceptor
Draft

feat: add gRPC interceptor support as library-grpc module#1631
MRezaNasirloo wants to merge 3 commits into
ChuckerTeam:mainfrom
MRezaNasirloo:feature/grpc-interceptor

Conversation

@MRezaNasirloo
Copy link
Copy Markdown

Adds ChuckerGrpcInterceptor in a new optional library-grpc module so gRPC calls are visible alongside HTTP traffic in the Chucker UI. A matching library-grpc-no-op module provides the release-build no-op.

  • library-grpc: real interceptor — captures all four gRPC call types (unary, server-stream, client-stream, bidi-stream), headers, trailers, request/response bodies, status codes and errors
  • library-grpc-no-op: source-compatible stub that passes calls through
  • sample: embedded Netty server + GrpcTask demo exercising all call types
  • API dump placeholders added; run ./gradlew apiDump to regenerate

📷 Screenshots

📄 Context

📝 Changes

📎 Related PR

🚫 Breaking

🛠️ How to test

⏱️ Next steps

@MRezaNasirloo MRezaNasirloo requested a review from a team as a code owner May 21, 2026 13:24
@MRezaNasirloo MRezaNasirloo marked this pull request as draft May 21, 2026 13:24
Adds ChuckerGrpcInterceptor to library and library-no-op so gRPC calls
are visible alongside HTTP traffic in the Chucker UI.

Usage:
  val channel = OkHttpChannelBuilder.forAddress(host, port)
      .intercept(ChuckerGrpcInterceptor(collector, context))
      .build()

Swap library-no-op's no-op variant for release builds — constructor
signatures match exactly for source-level compatibility.

Supported call types: unary, server-streaming, client-streaming,
bidirectional streaming. Streaming responses update live as messages
arrive rather than waiting for the stream to close.

Features:
- Request/response headers (with optional redaction)
- Body capture up to maxContentLength (default 250 KB), truncated if exceeded
- gRPC status code and description
- Response trailers shown alongside response headers
- Duration tracking

grpc-api is a compileOnly dependency; users who integrate gRPC already
have it on their classpath, and users who don't are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MRezaNasirloo MRezaNasirloo force-pushed the feature/grpc-interceptor branch from 5b3cc14 to c0d636e Compare May 21, 2026 13:38
MRezaNasirloo and others added 2 commits May 21, 2026 16:16
protobuf-gradle-plugin 0.9.x Kotlin DSL requires create() not id();
the id() extension needs an import that was unavailable after cleanup.
Also drops redundant id("java") — Android handles javalite automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire owns src/main/proto (pokemon.proto → Kotlin via Wire runtime).
Protobuf-gradle-plugin must not overlap that directory or both tools
generate classes in the same package, causing duplicate-class errors.

- Move sample_service.proto to src/main/grpc (protobuf-plugin's dir)
- Redirect protobuf-plugin Android source set proto dirs to src/main/grpc
  via configure<AppExtension> so Wire never sees the gRPC proto
- Add builtins { java } to generate HelloRequest/HelloReply message classes
- Add grpc-protobuf dep (ProtoUtils marshaller used by generated stubs)
- Add javax.annotation-api (compileOnly) — @generated removed from JDK 9+
- Exclude protobuf-javalite globally: play-services-cronet pulls in an old
  version that conflicts with protobuf-java (which is a superset)
- Fix detekt: extract magic numbers to constants, narrow exception types,
  suppress LongParameterList on Composables (known Compose pattern)
- Fix ktlint style violations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cortinico cortinico requested a review from Copilot May 22, 2026 10:00
@cortinico
Copy link
Copy Markdown
Member

Adds ChuckerGrpcInterceptor in a new optional library-grpc module so gRPC calls are visible alongside HTTP traffic in the Chucker UI. A matching library-grpc-no-op module provides the release-build no-op.

Before sending a PR like this, why haven't you opened a GH issue to discuss it?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds gRPC traffic capture to Chucker by introducing a ChuckerGrpcInterceptor (with a no-op counterpart) and a sample app demo that exercises all four gRPC call types, so gRPC calls can be inspected alongside HTTP transactions in the Chucker UI.

Changes:

  • Added ChuckerGrpcInterceptor (real) and a source-compatible no-op ChuckerGrpcInterceptor implementation.
  • Extended the sample app with an embedded gRPC server + client task and UI controls to trigger gRPC calls.
  • Updated Gradle version catalog and sample build configuration to generate gRPC stubs via protobuf/protoc.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
sample/src/main/res/values/strings.xml Adds UI string for the new gRPC demo action.
sample/src/main/kotlin/com/chuckerteam/chucker/sample/SampleGrpcServer.kt Adds an embedded gRPC server implementing unary + streaming call types.
sample/src/main/kotlin/com/chuckerteam/chucker/sample/SampleApplication.kt Starts/stops the embedded gRPC server and exposes a ChuckerCollector.
sample/src/main/kotlin/com/chuckerteam/chucker/sample/MainActivity.kt Wires a new “Do gRPC activity” action into the UI and manages task lifecycle.
sample/src/main/kotlin/com/chuckerteam/chucker/sample/GrpcTask.kt Adds a demo gRPC client that makes unary/streaming calls through ChuckerGrpcInterceptor.
sample/src/main/kotlin/com/chuckerteam/chucker/sample/compose/testtags/ChuckerTestTags.kt Adds a test tag for the new gRPC button.
sample/src/main/kotlin/com/chuckerteam/chucker/sample/compose/ChuckerSampleMainScreen.kt Adds onDoGrpc callback plumbing to the main Compose screen.
sample/src/main/kotlin/com/chuckerteam/chucker/sample/compose/ChuckerSampleControls.kt Adds a gRPC button to the control list and tags.
sample/src/main/grpc/sample_service.proto Defines the gRPC service and messages used by the sample.
sample/src/main/AndroidManifest.xml Registers SampleApplication.
sample/build.gradle.kts Adds protobuf + gRPC dependencies and configures protoc/grpc codegen for the sample.
library/src/main/kotlin/com/chuckerteam/chucker/api/ChuckerGrpcInterceptor.kt Implements the gRPC ClientInterceptor that records gRPC activity into HttpTransaction.
library/build.gradle.kts Adds (compileOnly) gRPC API dependency for the new interceptor.
library/api/library.api Updates the public API dump to include ChuckerGrpcInterceptor.
library-no-op/src/main/kotlin/com/chuckerteam/chucker/api/ChuckerGrpcInterceptor.kt Adds the no-op pass-through interceptor variant.
library-no-op/build.gradle.kts Adds (compileOnly) gRPC API dependency for the no-op interceptor.
library-no-op/api/library-no-op.api Updates the no-op public API dump to include ChuckerGrpcInterceptor.
gradle/libs.versions.toml Adds gRPC/protobuf versions and library/plugin coordinates.
CHANGELOG.md Documents the new ChuckerGrpcInterceptor feature.
Comments suppressed due to low confidence (1)

library/src/main/kotlin/com/chuckerteam/chucker/api/ChuckerGrpcInterceptor.kt:85

  • Potential race: collector.onRequestSent(transaction) inserts the transaction asynchronously and assigns transaction.id later. onMessage()/onClose() call collector.onResponseReceived(transaction) immediately after ensureStarted(), but updateTransaction() will be a no-op until the insert finishes, so early/fast gRPC responses can be dropped (including the final response if close happens quickly). Ensure the insert completes before issuing any updates (e.g., await insertion or provide a collector API that guarantees ordering).
        fun ensureStarted() {
            if (transactionStarted.compareAndSet(false, true)) {
                collector.onRequestSent(transaction)
            }
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sample/build.gradle.kts
Comment on lines +71 to +82
// Wire owns src/main/proto (pokemon.proto). Redirect protobuf-plugin to src/main/grpc so the two
// tools never process the same files, avoiding duplicate-class errors (Wire→Kotlin, protoc→Java).
configure<com.android.build.gradle.AppExtension> {
sourceSets.getByName("main") {
@Suppress("UNCHECKED_CAST")
val proto =
(this as org.gradle.api.plugins.ExtensionAware)
.extensions
.getByName("proto") as org.gradle.api.file.SourceDirectorySet
proto.setSrcDirs(listOf(file("src/main/grpc")))
}
}
Comment thread library/build.gradle.kts
Comment on lines 98 to 101
api(libs.okhttp)
api(libs.okhttp3.okhttp)
compileOnly(libs.grpc.api)
testImplementation(libs.mockwebserver)
Comment on lines 53 to 57
dependencies {
api(libs.okhttp)
compileOnly(libs.grpc.api)
implementation(libs.jetbrains.kotlin.stdlib)
}
Comment on lines +35 to +56
fun execute() {
scope.launch {
channel =
OkHttpChannelBuilder
.forAddress("localhost", SampleApplication.GRPC_PORT)
.usePlaintext()
.intercept(interceptor)
.build()

val blocking = SampleGreeterGrpc.newBlockingStub(channel)
val async = SampleGreeterGrpc.newStub(channel)

runUnary(blocking)
delay(CALL_DELAY_MS)
runServerStream(blocking)
delay(CALL_DELAY_MS)
runClientStream(async)
delay(CALL_DELAY_MS)
runBidiStream(async)

channel.shutdown().awaitTermination(SHUTDOWN_TIMEOUT_SEC, TimeUnit.SECONDS)
}
Comment on lines +64 to +68
val authority = next.authority() ?: "unknown"
transaction.host = authority.substringBefore(":")
transaction.path = "/${method.fullMethodName}"
transaction.scheme = if (authority.substringAfterLast(':').toIntOrNull() == HTTPS_PORT) "https" else "http"
transaction.url = "${transaction.scheme}://$authority${transaction.path}"
Comment thread CHANGELOG.md
Comment on lines 10 to +11
- Long-press the payload copy button to choose between copying the raw body or the formatted body [#1613]
- `ChuckerGrpcInterceptor` — a gRPC `ClientInterceptor` that captures all four gRPC call types (unary, server-streaming, client-streaming, bidirectional) and displays them alongside HTTP traffic in Chucker. Streaming responses update live as messages arrive. Add it to your channel via `OkHttpChannelBuilder.intercept(ChuckerGrpcInterceptor(collector, context))`. Swap `library-no-op`'s no-op variant for release builds.
@MRezaNasirloo
Copy link
Copy Markdown
Author

Adds ChuckerGrpcInterceptor in a new optional library-grpc module so gRPC calls are visible alongside HTTP traffic in the Chucker UI. A matching library-grpc-no-op module provides the release-build no-op.

Before sending a PR like this, why haven't you opened a GH issue to discuss it?

@cortinico Hey there! This is still a work in progress. I accidentally opened this PR. I’m reworking it to remove the new modules so users don’t have to add them manually. I’ve set it to draft, but feel free to close it if you’d like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants