Skip to content

refactor: replace custom compression with native gRPC compression - #2328

Merged
EliasiOfir merged 2 commits into
mainfrom
claude/pr-2194-review-reimplement-pohtq1
Aug 20, 2026
Merged

EliasiOfir merged 2 commits into
mainfrom
claude/pr-2194-review-reimplement-pohtq1

Conversation

@EliasiOfir

Copy link
Copy Markdown
Collaborator

Description

This PR replaces the custom application-level compression implementation with native gRPC compression, which is more efficient and standards-compliant. The changes simplify the codebase by removing custom compression headers and logic from the consumer and provider, delegating compression to gRPC's built-in mechanisms.

Key Changes

Removed

  • Custom compression headers: LavaCompressionSupportHeader and LavaCompressionHeader are no longer used
  • Application-level compression logic: Removed from RPCProviderServer.Relay() and RPCConsumerServer.relayInner()
  • Compression integration tests: protocol/common/compression_integration_test.go (custom compression flow tests)
  • CORS validation tests: protocol/rpcprovider/cors_test.go (no longer needed with new architecture)
  • CORS check functionality: Removed from provider testing utilities

Added

  • Provider listener refactor: Complete rewrite of ProviderListener to use connection multiplexing (cmux) for serving both gRPC and HTTP/1.1 on the same port
    • gRPC connections are routed by HTTP/2 client preface matching
    • HTTP health checks are served on a separate HTTP/1.1 listener
    • Proper TLS configuration with ALPN support for protocol negotiation
  • gRPC health checking: Integrated google.golang.org/grpc/health for proper gRPC health status reporting
  • Comprehensive provider listener tests: protocol/rpcprovider/provider_listener_test.go with coverage for:
    • Relay and health check functionality (TLS and plaintext)
    • Health check path validation
    • Unregistered receiver handling
    • Graceful shutdown behavior
    • Native gRPC compression verification

Modified

  • Consumer compression flag: Changed from enable-application-level-compression to enable-grpc-compression, now controls gRPC-level compression negotiation
  • Compression utilities: Updated comments to clarify that compression.go is for stored payloads only; wire compression is handled by gRPC
  • Provider server: Removed custom compression header checks and compression logic
  • Consumer server: Removed custom compression header setting and decompression logic

Technical Details

The new architecture leverages gRPC's native compression support:

  • Consumers dial providers with grpc.UseCompressor(gzip) when the enable-grpc-compression flag is set
  • Providers automatically echo back the consumer's compressor on responses
  • No custom headers or manual compression/decompression needed
  • Connection multiplexing allows serving gRPC and HTTP health checks on the same port with proper protocol negotiation via ALPN

Testing

  • Added comprehensive test suite for provider listener covering both TLS and plaintext modes
  • Tests verify gRPC compression is applied when requested and omitted otherwise
  • Existing provider and consumer tests continue to pass with the simplified compression model
  • Removed obsolete compression integration and CORS tests

Author Checklist

  • read the contribution guide
  • included the correct type prefix in the PR title
  • confirmed ! in the type prefix if API or client breaking change
  • targeted the main branch
  • provided a link to the relevant issue or specification
  • reviewed "Files changed" and left comments if necessary
  • included the necessary unit and integration tests
  • updated the relevant documentation or specification, including comments for documenting Go code
  • confirmed all CI checks have passed

Reviewers Checklist

  • confirmed the correct type prefix in the PR title
  • confirmed all author checklist items have been addressed
  • reviewed state machine logic, API design and naming, documentation is accurate, tests and test coverage

https://claude.ai/code/session_01Ka44Dzff68tLrAQ5SJPmaC

EliasiOfir and others added 2 commits August 16, 2026 09:56
The provider listener wrapped its gRPC server in grpc-web and h2c so that
browser based lava-sdk clients could reach it. The SDK is gone, so the
wrapper only costs us the supported grpc.Server.Serve path: native
connection management, graceful stop, and no protocol translation.

The listener now runs a real grpc.Server, with cmux routing connections by
the protocol they open with: the HTTP/2 client preface goes to gRPC,
everything else to the HTTP/1.1 health endpoint that kubernetes httpGet
probes use. Matching the preface is a cheap prefix compare and resolves at
connection time, unlike matching the gRPC content-type which would have to
wait for the first request. The gRPC health checking protocol is registered
alongside it for native clients and load balancers, and Shutdown now drains
both servers, reports NOT_SERVING while draining, and releases the port.

When TLS is enabled the listener advertises ALPN explicitly. gRPC clients
offer only "h2" and refuse a connection that negotiated no protocol at all,
so terminating TLS without advertising it would have broken every consumer
of a TLS enabled provider. "http/1.1" is listed first because ALPN resolves
in server preference order, which keeps HTTP health probes on HTTP/1.1
while gRPC clients still get "h2".

Relay compression is left to gRPC as well. The consumer already dials with
grpc.UseCompressor(gzip) when compression is enabled, and the server echoes
that compressor back on the response, so the hand rolled compress/decompress
pass and its lava-compression headers were compressing already compressed
payloads. The flag is renamed to --enable-grpc-compression accordingly, with
the old name still accepted so existing deployments keep working. The gzip
helpers stay for the cache, which compresses entries at rest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ka44Dzff68tLrAQ5SJPmaC
PerformCORSCheck required every provider to answer an HTTPS request on any
path with Access-Control-Allow-Origin and an Access-Control-Allow-Headers
list containing x-grpc-web. Those headers existed only so browsers could
preflight grpc-web calls, and the provider listener no longer speaks
grpc-web, so the check now fails against every provider.

It is a hard failure in both callers: lavap test rpcprovider reports
"invalid CORS check" for every endpoint, and the health monitor marks every
provider unhealthy. Nothing else is lost by removing it, both callers probe
the endpoint over gRPC first, and TLS reachability is already covered by
ConnectRawClientWithTimeout and its insecure retry.

The cors_test.go scaffolding went with it, including the self signed
certificate it wrote into the package directory on every run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ka44Dzff68tLrAQ5SJPmaC
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Refactor: replace custom relay compression with native gRPC gzip + cmux listener

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Replace custom relay compression headers with native gRPC gzip compression.
• Refactor provider listener to serve gRPC and HTTP health checks via cmux + ALPN.
• Rename compression flag with deprecated alias; add comprehensive provider listener tests.
Diagram

graph TD
  C["RPC Consumer"] --> D["ConnectGRPCClient (gzip opt)"] --> L["ProviderListener"] --> M{"cmux route"} --> G["gRPC Server (Relayer + Health)"] --> R["RelayReceiver"]
  P["K8s HTTP probe"] --> L --> M --> H["HTTP health handler"]
  B["gRPC LB probe"] --> L --> M --> G
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Serve HTTP health on a separate port
  • ➕ Removes cmux complexity and protocol sniffing timeouts
  • ➕ Simplifies TLS/ALPN concerns (one protocol per listener)
  • ➖ Requires Kubernetes/deployment changes and extra port exposure
  • ➖ Breaks existing single-port assumptions/configuration
2. Use only gRPC health checking (drop HTTP endpoint)
  • ➕ Pure gRPC, standards-based probing for LBs/clients
  • ➕ Less surface area (no HTTP server)
  • ➖ Kubernetes httpGet probes would need migration to gRPC probes (not always available)
  • ➖ Harder backward compatibility for existing deployments
3. Keep grpc-web/h2c wrapper for the provider listener
  • ➕ Retains browser/grpc-web compatibility if ever needed again
  • ➖ Adds protocol translation overhead and limits native grpc.Server lifecycle features
  • ➖ Keeps CORS and grpc-web-specific concerns in core provider serving path

Recommendation: Current approach is the best fit if you must keep a single port that supports both Kubernetes httpGet probes and native gRPC clients. cmux routing by HTTP/2 preface is efficient and avoids waiting for a gRPC request’s content-type, and the explicit ALPN advertisement prevents TLS regressions for real gRPC clients. Keeping the deprecated flag alias is a pragmatic upgrade path; consider a future removal plan once deployments migrate.

Files changed (12) +479 / -195

Enhancement (1) +136 / -45
provider_listener.goRewrite provider listener to native gRPC + cmux + health servers +136/-45

Rewrite provider listener to native gRPC + cmux + health servers

• Replaces grpc-web/h2c wrapper with a native grpc.Server and adds cmux to multiplex gRPC and HTTP/1.1 health checks on one port. Adds gRPC health checking, gzip compressor registration, explicit TLS ALPN configuration, and a shutdown flow that drains connections and reports NOT_SERVING.

protocol/rpcprovider/provider_listener.go

Refactor (5) +7 / -141
compression.goClarify compression scope; remove custom wire headers +2/-8

Clarify compression scope; remove custom wire headers

• Documents that this package is for stored payload compression (e.g., caches). Removes the custom application-level compression header constants now that wire compression is handled by gRPC.

protocol/common/compression.go

health.goRemove provider CORS validation from health checks +0/-6

Remove provider CORS validation from health checks

• Eliminates the PerformCORSCheck call during provider probing. This matches the removal of grpc-web/CORS requirements in the provider serving path.

protocol/monitoring/health.go

rpcconsumer_server.goRemove custom compression metadata and manual decompression +3/-25

Remove custom compression metadata and manual decompression

• Stops sending the custom compression support header to providers. Removes response header inspection and manual decompression since gRPC handles compression transparently.

protocol/rpcconsumer/rpcconsumer_server.go

rpcprovider_server.goRemove application-level relay compression logic +2/-28

Remove application-level relay compression logic

• Deletes custom header parsing for consumer compression support and removes manual gzip compression plus response header injection. Leaves compression to gRPC negotiation driven by the consumer dial options.

protocol/rpcprovider/rpcprovider_server.go

testing.goRemove CORS check utilities from provider testing flows +0/-74

Remove CORS check utilities from provider testing flows

• Deletes PerformCORSCheck and related header validation helpers, and removes CORS checks from the provider testing/probing logic.

protocol/rpcprovider/testing.go

Tests (1) +321 / -0
provider_listener_test.goAdd end-to-end tests for cmux listener, health, shutdown, and gzip +321/-0

Add end-to-end tests for cmux listener, health, shutdown, and gzip

• Introduces comprehensive tests covering relay/probe calls over plaintext and TLS, HTTP health path/method validation, unregistered receiver behavior, graceful shutdown/port release, and verification that gRPC gzip reduces on-wire bytes when enabled.

protocol/rpcprovider/provider_listener_test.go

Other (5) +15 / -9
.gitignoreStop ignoring provider test TLS cert/key files +0/-4

Stop ignoring provider test TLS cert/key files

• Removes ignores for protocol/rpcprovider/cert.pem and key.pem. This aligns with deleting the CORS tests that generated local TLS artifacts in that directory.

.gitignore

go.modAdd cmux dependency for connection multiplexing +1/-0

Add cmux dependency for connection multiplexing

• Introduces github.com/soheilhy/cmux to allow serving gRPC and HTTP health on the same port.

go.mod

go.sumRecord cmux (and transitive) checksums +3/-0

Record cmux (and transitive) checksums

• Adds go.sum entries for github.com/soheilhy/cmux v0.1.5 (and a referenced x/net go.mod version).

go.sum

consumer_types.goRename compression flag and keep deprecated alias +6/-2

Rename compression flag and keep deprecated alias

• Renames the consumer/provider compression flag to enable-grpc-compression. Adds a deprecated constant for the previous flag name to preserve backward compatibility.

protocol/lavasession/consumer_types.go

rpcconsumer.goEnable gRPC compression via new flag (with deprecated fallback) +5/-3

Enable gRPC compression via new flag (with deprecated fallback)

• Reads both enable-grpc-compression and the deprecated enable-application-level-compression flag. Updates CLI help text and logs to describe gzip compression at the gRPC layer.

protocol/rpcconsumer/rpcconsumer.go

@codecov

codecov Bot commented Aug 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.62162% with 21 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
protocol/rpcprovider/provider_listener.go 76.47% 11 Missing and 5 partials ⚠️
protocol/rpcconsumer/rpcconsumer.go 0.00% 5 Missing ⚠️
Flag Coverage Δ
consensus 8.96% <ø> (ø)
protocol 38.80% <71.62%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
protocol/common/compression.go 59.25% <ø> (ø)
protocol/lavasession/consumer_types.go 77.13% <ø> (ø)
protocol/monitoring/health.go 0.00% <ø> (ø)
protocol/rpcconsumer/rpcconsumer_server.go 32.18% <100.00%> (+0.15%) ⬆️
protocol/rpcprovider/rpcprovider_server.go 11.60% <ø> (+0.20%) ⬆️
protocol/rpcprovider/testing.go 0.00% <ø> (-9.43%) ⬇️
protocol/rpcconsumer/rpcconsumer.go 8.64% <0.00%> (-0.04%) ⬇️
protocol/rpcprovider/provider_listener.go 75.20% <76.47%> (+39.91%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. gRPC-Web routing removed 🐞 Bug ≡ Correctness
Description
ProviderListener now routes all non-HTTP/2 connections to an HTTP handler that only serves GET on
the health path, so gRPC-Web requests (HTTP/1.1 POST with application/grpc-web content-types) will
be rejected (typically 404) instead of reaching the Relayer service.
Code

protocol/rpcprovider/provider_listener.go[R171-175]

+	pl.httpServer = &http.Server{
+		ReadHeaderTimeout: connectionMatchTimeout,
+		Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+			if req.URL.Path != healthCheckPath {
+				resp.WriteHeader(http.StatusNotFound)
Evidence
cmux routes HTTP/2 to the gRPC server and everything else to the HTTP server; the HTTP server
handler only answers GET on the configured health path, so HTTP/1.1 gRPC-Web requests cannot reach
the gRPC server. The repo’s grpcwebcompat package is specifically designed to serve gRPC-Web
requests, highlighting the behavioral regression when it’s no longer used here.

protocol/rpcprovider/provider_listener.go[144-185]
protocol/grpcwebcompat/grpcwebcompat.go[1-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The provider listener no longer forwards HTTP/1.1 requests to a gRPC-Web-compatible handler. Because cmux routes only HTTP/2 connections to the gRPC server, any gRPC-Web client (HTTP/1.1) will hit the health-only HTTP handler and fail all RPCs.

## Issue Context
`grpcwebcompat.WrapServer` exists to correctly serve gRPC-Web requests; previously the provider listener used it, but the new implementation returns 404/405 for anything except the health endpoint.

## Fix Focus Areas
- protocol/rpcprovider/provider_listener.go[144-185]

### Suggested direction
- Keep the dedicated health endpoint behavior for `GET healthCheckPath`.
- For other HTTP/1.1 requests, route them to a `grpcwebcompat.WrapServer(pl.grpcServer)` handler (and if browser clients are expected, restore the required CORS headers / OPTIONS handling).
- If gRPC-Web support is intentionally being dropped, explicitly document that and remove/disable any remaining grpc-web expectations in configs/docs/tests.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +171 to +175
pl.httpServer = &http.Server{
ReadHeaderTimeout: connectionMatchTimeout,
Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if req.URL.Path != healthCheckPath {
resp.WriteHeader(http.StatusNotFound)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Grpc-web routing removed 🐞 Bug ≡ Correctness

ProviderListener now routes all non-HTTP/2 connections to an HTTP handler that only serves GET on
the health path, so gRPC-Web requests (HTTP/1.1 POST with application/grpc-web content-types) will
be rejected (typically 404) instead of reaching the Relayer service.
Agent Prompt
## Issue description
The provider listener no longer forwards HTTP/1.1 requests to a gRPC-Web-compatible handler. Because cmux routes only HTTP/2 connections to the gRPC server, any gRPC-Web client (HTTP/1.1) will hit the health-only HTTP handler and fail all RPCs.

## Issue Context
`grpcwebcompat.WrapServer` exists to correctly serve gRPC-Web requests; previously the provider listener used it, but the new implementation returns 404/405 for anything except the health endpoint.

## Fix Focus Areas
- protocol/rpcprovider/provider_listener.go[144-185]

### Suggested direction
- Keep the dedicated health endpoint behavior for `GET healthCheckPath`.
- For other HTTP/1.1 requests, route them to a `grpcwebcompat.WrapServer(pl.grpcServer)` handler (and if browser clients are expected, restore the required CORS headers / OPTIONS handling).
- If gRPC-Web support is intentionally being dropped, explicitly document that and remove/disable any remaining grpc-web expectations in configs/docs/tests.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown

Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
7 files   ±0   0 ❌ ±0 

Results for commit 3f786d3. ± Comparison against base commit e293c30.

♻️ This comment has been updated with latest results.

@EliasiOfir
EliasiOfir merged commit 5dbc50a into main Aug 20, 2026
37 of 38 checks passed
@EliasiOfir
EliasiOfir deleted the claude/pr-2194-review-reimplement-pohtq1 branch August 20, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants