Conversation
A Connect-protocol streaming client never looked at the response content-type. The gRPC arms of make_server_stream are gated through check_grpc_response_head and the Connect unary path has its own inline check, but the Connect arm of make_server_stream (call_server_stream and BidiStream) and parse_connect_client_stream_response (call_client_stream) constructed the stream or read the body whatever the header said. Connect and gRPC share envelope framing, so a 200 application/grpc+proto reply was decoded as Connect and its data envelopes handed to the caller as messages until the body ended without END_STREAM, and a proxy's 200 text/html error page was parsed as a five-byte envelope header and reported as resource_exhausted, a code a retry policy treats as back-off rather than "the peer is not speaking Connect". Add validate_connect_response_content_type, one classifier for all three Connect shapes, and call it on the 200 path of both streaming parsers before the stream is constructed or the body read. HTTP-status precedence is unchanged: a non-200 Connect reply still parses its JSON error body first, so the gate only ever sees 200s. A Connect content-type for the shape but the wrong codec is internal; anything else is unknown. The rejection carries the response headers. A missing header stays accepted, as on the gRPC and unary paths; connect-go rejects it, but an intermediary that strips the header would otherwise fail an otherwise well-formed call. The unary Connect check now shares the classifier instead of being a third copy. Its message gains the "(expected ...)" suffix the gRPC message has, and it strips media-type parameters and matches exactly instead of by prefix, so application/protobuf for a proto client is now rejected. Tests: the two streaming matrix tests gain Connect rows and lose the grpc in their names, the row pinning the gap is gone, the HTTP-status precedence test gains a Connect case whose JSON error body is still parsed, the unary classification is pinned directly, and a new call_client_stream test drives text/html, application/grpc+proto and application/connect+json through the real call path. Fixes #275 Signed-off-by: Iain McGinniss <309153+iainmcgin@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Connect-protocol streaming client never looked at the response
content-type. The gRPC and gRPC-Web arms ofmake_server_streamhave been gated throughcheck_grpc_response_headsince #266, and the Connect unary path has its own inline check, but theProtocol::Connectarm ofmake_server_stream(behindcall_server_streamandBidiStream) andparse_connect_client_stream_response(behindcall_client_stream) constructed the stream or read the body whatever the header said. Connect and gRPC share envelope framing, so a 200application/grpc+protoreply was decoded as Connect and its data envelopes handed to the caller as messages until the body ended withoutEND_STREAM(internal), and a proxy's 200text/htmlerror page was parsed as an envelope header and reported asresource_exhausted("message size 1752460652 exceeds limit"). connect-go'sconnectStreamingClientConn.validateResponserejects both withunknownbefore reading a byte.This adds
validate_connect_response_content_type, one classifier for all three Connect shapes, called on the 200 path of both streaming parsers before the stream is constructed or the body read (a mismatched reply is no longer buffered up tomax_message_sizeeither). HTTP-status precedence is unchanged: a non-200 Connect reply still parses its JSON error body first. A content-type that is a Connect type for the shape (application/connect+*for a stream,application/proto/application/jsonfor unary) but not the configured codec's isinternal; anything else isunknown— connect-go's rule for streams. The rejection carries the response headers, andcall_server_stream/call_client_stream/BidiRecvHalf::messagedocument the gate under# Errors.A missing
content-typeheader stays accepted. connect-go rejects it, but the gRPC validator here accepts it for proxy compatibility and the unary Connect path always has; the cost, stated on the classifier, is that a header-less non-Connect body still fails on its framing.The unary Connect check now shares the classifier. Two observable consequences: its message gains the
(expected …)suffix the gRPC message already had, and it strips media-type parameters and matches exactly instead of by prefix, soapplication/protobuffor a proto client — accepted by the oldstarts_with("application/proto")— now fails withunknown. The changelog fragment names that signature.Tests: the streaming matrix tests gain
Protocol::Connectrows; the row that pinned the gap is gone; the status-precedence test gains a Connect 502text/htmlcase whose JSON error body is still parsed; the unary classification is pinned directly; a newcall_client_streamtest drivestext/html,application/grpc+protoandapplication/connect+jsonthrough the real call path.Fixes #275
Deferred review findings
Protocol::detect_from_content_type; a crate-wide decision.validate_grpc_response_content_type; sharing it means touching the gRPC validator, deliberately left alone here.content-typerenders as empty in the message (pre-existing in the gRPC validator).is_streaming: boolis a positional bool at three private call sites; the test module now has two canned-200 transports that could merge; client-stream non-200 precedence is pinned only throughmake_server_stream.Gates: clippy 1.95.0
-D warningsclean;cargo test --workspace938 passed;cargo test -p connectrpc --no-default-features514 passed; rustdoc-Dwarningsclean; nightly fmt check clean;task conformance:test-client-connect-only2580 passed, 0 failed.