Skip to content

Reject unknown Connect stream compression with unimplemented - #365

Merged
i2y merged 6 commits into
mainfrom
reject-unknown-connect-stream-compression
Sep 27, 2026
Merged

i2y merged 6 commits into
mainfrom
reject-unknown-connect-stream-compression

Conversation

@i2y

@i2y i2y commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Connect streaming requests with an unsupported connect-content-encoding are currently treated as identity. A compressed message then fails with internal ("sent compressed message without compression support"), and an uncompressed one goes through.

The spec asks servers to handle this header like content-encoding and answer unimplemented with the supported encodings. connect-go does this for every protocol, and connect-py already does it for Connect unary and gRPC, so this brings Connect streams in line.

While at it, request compression now works the same way on every path:

  • An empty encoding (an empty content-encoding or grpc-encoding header, or an empty compression query parameter) means identity, as in connect-go. Connect unary and gRPC used to reject it with unimplemented, while Connect streams accepted it.
  • Names are matched exactly, as in connect-go. Connect unary POST used to lowercase content-encoding, so a compression registered as Xor worked for streams but failed unary requests. GZIP is now rejected there too, as it already was on the other paths.
  • Every path builds the error with the new unknown_compression_error. Like connect-go, it lists only real compressions, so identity no longer shows up. When none are configured, it says compression is not supported instead of ending with an empty list.

Conformance only has unexpected-compression cases for Connect unary and gRPC/gRPC-Web, which is likely why this slipped through, so a Connect streaming case belongs there too.

A heads-up for #348: _server_anyio.py has its own copy of this check with the old message, so whichever PR lands second may want to switch it to unknown_compression_error.

The new behavior tests in test_compression.py fail on main (20 of 30) and pass here. uv run poe check and the server conformance suites pass too.

i2y and others added 3 commits September 26, 2026 22:47
A Connect streaming request with an unsupported connect-content-encoding
was treated as identity: a compressed message then failed with internal
("sent compressed message without compression support"), and an
uncompressed one went through. The spec asks servers to handle this
header like content-encoding and answer unimplemented with the supported
encodings, as connect-go does and as connect-py already does for Connect
unary and gRPC.

An empty header still means identity, as in connect-go. The stream path
now reuses the unary error message, so gRPC and gRPC-Web errors list the
supported encodings too.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: i2y <6240399+i2y@users.noreply.github.com>
An empty content-encoding, grpc-encoding, or GET compression parameter
was rejected with unimplemented on every path except Connect streams.
Treat it as identity everywhere, as connect-go does, so all paths handle
request compression the same way.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: i2y <6240399+i2y@users.noreply.github.com>
@i2y
i2y marked this pull request as ready for review September 26, 2026 23:09

@stefanvanburen stefanvanburen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looks right to me, just had some small suggestions / nits. definitely would be nice to send an issue/PR for stream compression upstream to connectrpc/conformance; I would think that would have caught these issues

Comment thread src/connectrpc/_server_async.py Outdated

# Handle compression if specified
compression_name = headers.get("content-encoding", "identity").lower()
compression_name = (headers.get("content-encoding") or "identity").lower()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pre-existing, but should we drop the .lower() here and in the sync server code? Looks like neither the stream path nor connect-go do this.

@i2y i2y Sep 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, dropped it in a77bba7. It turned out to be more than an inconsistency: a compression registered under a name with uppercase letters (say Xor) worked for streams, but unary requests failed with unknown compression: 'xor': supported encodings are Xor, identity. I added a test for that on both servers.

Comment thread src/connectrpc/_server_async.py Outdated
raise ConnectError(
Code.UNIMPLEMENTED, "Unrecognized request compression"
Code.UNIMPLEMENTED,
f"unknown compression: '{compression_name}': supported encodings are {', '.join(self._compressions.keys())}",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we have this same f-string in quite a few places; could extract to a helper. Also, we mention identity (assuming _default_compressions), which feels a bit weird?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in a4df3eb and 15c8062.

The message now comes from unknown_compression_error in _compression.py, used in all six places.

Agreed on identity as well. It only showed up because resolve_compressions adds it for lookups, and connect-go and connect-es list only real compressions. It's no longer listed at all. With compression disabled, the message now says compression is not supported, where connect-go ends it with an empty list.

This change makes #361 conflict in the sync server's unary POST and GET paths (the lines right after the raise), but keeping both sides resolves it.

i2y and others added 3 commits September 27, 2026 09:38
The Connect unary POST path lowercased content-encoding before looking
it up, unlike every other path, connect-go, and connect-es. A
compression registered under a name with uppercase letters therefore
worked for streams but failed unary requests with unimplemented.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: i2y <6240399+i2y@users.noreply.github.com>
The same unknown compression message was built in six places. Build it
in one helper, and leave identity out of the supported encodings unless
nothing else is supported: identity is always accepted, it only showed
up because resolve_compressions adds it for lookups, and connect-go and
connect-es list only real compressions.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: i2y <6240399+i2y@users.noreply.github.com>
With no compression configured, the unknown compression error still
listed identity as the only supported encoding. identity is always
accepted and is not a compression, so say that compression is not
supported instead of listing it or ending with an empty list.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: i2y <6240399+i2y@users.noreply.github.com>
@i2y

i2y commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor Author

@stefanvanburen

Thanks for the review!

looks right to me, just had some small suggestions / nits. definitely would be nice to send an issue/PR for stream compression upstream to connectrpc/conformance; I would think that would have caught these issues

Agreed on conformance. I'll open an issue on connectrpc/conformance to add a Connect streaming unexpected-compression case (unknown connect-content-encoding → unimplemented) next to the existing unary and gRPC/gRPC-Web ones. I'll leave empty encodings out of it, since implementations differ there: connect-go treats an empty header as identity, while connect-es rejects it.

@i2y
i2y merged commit d26b217 into main Sep 27, 2026
24 checks passed
@i2y
i2y deleted the reject-unknown-connect-stream-compression branch September 27, 2026 13:04
i2y added a commit that referenced this pull request Sep 27, 2026
Each server path looked up the request compression on its own, and the
copies drifted apart: #365 fixed an unknown Connect stream compression
being treated as identity, Connect unary POST lowercasing the name, and
empty encodings being accepted on some paths only. Resolve the name in
resolve_request_compression instead, so every path treats an empty name
as identity and matches names exactly. No behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: i2y <6240399+i2y@users.noreply.github.com>
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.

2 participants