Conversation
sevlaan
force-pushed
the
optional-content-length
branch
from
September 15, 2026 12:56
5aa0bc2 to
fe7db77
Compare
NIOHTTPClient always sets a Content-Length header on unary requests. The header is optional over HTTP/2, which is the only protocol this client speaks, and neither grpc-go nor grpc-swift send it. It causes trouble behind a gRPC proxy that forwards incoming metadata to its own outgoing calls: Content-Length is not on grpc-go's reserved-header list, so the forwarded length is applied to a different body and the next hop resets the stream with PROTOCOL_ERROR. There is no way to suppress the header from application code today, because it is added below the interceptor chain. Add a ContentLength option to NIOHTTPClient, defaulting to .sent so that existing behaviour is unchanged, and cover the handler with the first unit tests for the NIO transport. Signed-off-by: Vsevolod Bespalov <vsevolod.bespalov@plata.work>
sevlaan
force-pushed
the
optional-content-length
branch
from
September 15, 2026 12:57
fe7db77 to
d320036
Compare
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.
Fixes #438.
ConnectUnaryChannelHandlersets aContent-Lengthheader on every unary request sent throughNIOHTTPClient.NIOHTTPClientspeaks HTTP/2 only, where the header is optional: DATA frames are self-delimiting andEND_STREAMmarks the end of the body. grpc-go and grpc-swift do not send it.It becomes a problem behind a gRPC proxy that forwards incoming metadata to its own outgoing calls (a common way to propagate auth and tracing headers).
Content-Lengthis not on grpc-go's reserved-header list, so it is forwarded too, and the next hop gets a length that does not match its own body. Envoy then resets the stream withPROTOCOL_ERRORbefore the handler runs. Moving a client from grpc-swift to connect-swift is enough to trigger this, with no server change.There is no way to suppress the header from the application today: it is added below the interceptor chain, and the only hook above it is subclassing
NIOHTTPClient, which is not viable across a module boundary.Change
A nested
NIOHTTPClient.ContentLengthoption, passed as the last initializer parameter:.sentis the default and keeps today's behaviour, so nothing changes for existing users. It follows the shape of the existingProtocolClientConfig.UnaryGEToption.Tests
ConnectNIOhad no unit test target, so this adds one:ConnectLibraryTestsnow also depends onConnectNIO(one line inPackage.swift), andConnectUnaryChannelHandlerTestsdrives the handler with anEmbeddedChannel— no server or sockets. Three cases: the header is present by default, absent with.omitted, andHostplus caller headers survive in both modes.swift buildand the new tests pass locally, andswiftlint lintis clean. I could not runmake testconformancehere (no Go toolchain on this machine), so I am relying on CI for it — the default path is unchanged, so I do not expect any movement there.Happy to switch to a plain
Boolor another spelling if you prefer.🤖 Generated with Claude Code