Skip to content

Send single-message requests as a sized body - #346

Merged
anuraaga merged 3 commits into
connectrpc:mainfrom
nblintao:send-single-message-requests-sized
Sep 24, 2026
Merged

anuraaga merged 3 commits into
connectrpc:mainfrom
nblintao:send-single-message-requests-sized

Conversation

@nblintao

Copy link
Copy Markdown
Contributor

Server-streaming calls, and unary calls over gRPC and gRPC-Web, send exactly one request message, but both clients hand it to the transport as a streamed body:

  • On HTTP/1.1 the request goes out with Transfer-Encoding: chunked, although its size is known before the call starts.
  • In the async client, pyqwest writes the headers and then pulls the body from an async generator, which runs on the event loop. If the loop is busy right after the call is made, the server has the headers but no body until the loop gets back to the generator. A proxy in front of the server that times out idle request bodies cuts such a request off.

This change encodes the message up front and passes the bytes to stream(), as unary Connect calls already do with post(), so the transport writes the whole request with a Content-Length. Client-streaming and bidi-streaming requests still stream. connect-go sends server-streaming requests the same way since connectrpc/connect-go#649, and connect-web sends them as a single body too.

Validation:

  • New tests record the content each kind of call hands to the transport, for the async and sync clients under all three protocols: bytes for unary and server-streaming calls, an iterator for client and bidi streams. They fail without the change.
  • On a real socket, with the event loop blocked for 2 s right after a server-streaming call starts: before this change the headers arrived with transfer-encoding: chunked and the body 2 s later; after it, headers and body arrive in the same read, with content-length.
  • uv run poe check and uv run poe test-conformance pass.

🤖 Generated with Claude Code

Server-streaming calls, and unary calls over gRPC and gRPC-Web, send one
request message, but the clients handed it to the transport as a streamed
body: chunked on HTTP/1.1, and in the async client produced by the event
loop only after the headers were written, so a busy loop left the server
with headers and no body.

Encode the message up front and pass the bytes, as unary Connect calls
already do, so the transport writes the whole request with a
Content-Length. Client- and bidi-streaming requests still stream.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Tao Lin <tao.lin@radixark.ai>
@nblintao
nblintao force-pushed the send-single-message-requests-sized branch from 1523ce8 to ef06bb5 Compare September 23, 2026 23:44
@nblintao
nblintao marked this pull request as ready for review September 23, 2026 23:44
Signed-off-by: Anuraag Agrawal <anuraaga@gmail.com>
Signed-off-by: Anuraag Agrawal <anuraaga@gmail.com>

@anuraaga anuraaga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @nblintao! I was worried this would be quite tricky but the approach looks pretty good. There is one point we have to remember for the future, if the public APIs accept list in the future for convenience reasons, I added a comment about it to not forget

@anuraaga
anuraaga merged commit 803989d into connectrpc:main Sep 24, 2026
23 checks passed
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