Skip to content

fix(client): validate response content-type - #2831

Open
Vishal-770 wants to merge 3 commits into
grpc:masterfrom
Vishal-770:fix-client-content-type-validation
Open

fix(client): validate response content-type#2831
Vishal-770 wants to merge 3 commits into
grpc:masterfrom
Vishal-770:fix-client-content-type-validation

Conversation

@Vishal-770

@Vishal-770 Vishal-770 commented Aug 21, 2026

Copy link
Copy Markdown

Fixes #2365.

Currently, if the gRPC client receives a response with an incorrect Content-Type (for instance, text/html from a proxy like Envoy returning a 502 Bad Gateway), it ignores the content type and proceeds to attempt decoding the HTML body as if it were a valid gRPC binary stream. This leads to confusing stream parsing errors, such as invalid compression flag: 60 ... while receiving response with status: 502 Bad Gateway.

According to the gRPC specification: "If the Content-Type does not begin with application/grpc, the client SHOULD NOT assume that the body contains gRPC messages, and MUST fail the RPC with a status of UNKNOWN (or the equivalent status code translated from the HTTP status code, if one is present)."

Solution

This PR adds a Content-Type validation step in tonic/src/client/grpc.rs during the initial create_response call.

We now check if the Content-Type header starts with application/grpc (which also properly accommodates application/grpc-web and application/grpc+proto). If it is missing or invalid, we immediately return an error. If there is a recognizable HTTP error code (e.g., 502), we utilize crate::status::infer_grpc_status to convert it to the appropriate gRPC status, but with a clear, custom error message stating that the Content-Type was invalid.

Copilot AI lite review requested due to automatic review settings August 21, 2026 19:55
@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 21, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: Vishal-770 / name: vishal-770 (cd50591)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@nathanielford

Copy link
Copy Markdown
Contributor

Thanks for putting this together! Before we can merge, there are a few edge cases and potential breakages we should address:

• Trailers-Only Responses: When a gRPC server returns an immediate error via a Trailers-Only response (where grpc-status is delivered directly in the headers), the server often omits content-type entirely since there is no body payload. Because this check currently runs before Status::from_header_map, it overwrites the real status code and message with a generic invalid content-type: error. We should ensure Status::from_header_map runs first; if a grpc-status header exists, we should respect it and skip the content-type check.
• Test Doubles & In-Memory Mocks: Any existing user test harnesses or mock servers that don't explicitly set Content-Type: application/grpc will start failing immediately once this lands. Unclear whether this would be ok.
• Streaming Call Timing: For server-streaming calls, failing immediately at client.stream_call().await changes behavior for consumers expecting errors during stream.message().await.

If we adjust the ordering so grpc-status header inspection takes priority, that should eliminate the risk of clobbering real errors while still catching proxy misconfigurations. All that said, because we are winding down Tonic development even if we merge this there is no guarantee we'll have another release that will have it. Given that, let me know if you'd like to proceed!

This checks that the response Content-Type begins with application/grpc and returns an error immediately if it does not, avoiding confusing decoding errors.

Fixes grpc#2365
Trailers-Only responses deliver grpc-status directly in the initial headers and often omit the content-type header because there is no body payload.

Prioritizing Status::from_header_map ensures real status codes and error messages are preserved and not overwritten with generic invalid content-type errors. Content-type validation now only runs when no grpc-status header is present.
@Vishal-770
Vishal-770 force-pushed the fix-client-content-type-validation branch from 4493613 to 1aa5fdc Compare September 3, 2026 05:32
@Vishal-770

Copy link
Copy Markdown
Author

Thanks @nathanielford for pointing that out, that makes total sense!

I just pushed an update to address this. The ordering is flipped now so Status::from_header_map runs first—if a grpc-status header is present (like in Trailers-Only responses), we return that directly and skip the content-type check entirely so real server errors don't get overwritten. The content-type validation now only runs when there's no grpc-status in the headers.

Also added an integration test for Trailers-Only responses without a content-type header to make sure that case stays covered.

And totally get it regarding Tonic winding down! I'd still love to get this merged into master if you're happy with the changes.

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.

client must not attempt decode responses with wrong content type

3 participants