From 2b0e1cb6c591a625ec15aba92ace9d114900916d Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Tue, 21 Oct 2025 16:33:56 +0900 Subject: [PATCH 1/2] Do multiple reads for WSGI even with content length Signed-off-by: Anuraag Agrawal --- src/connectrpc/_server_sync.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/connectrpc/_server_sync.py b/src/connectrpc/_server_sync.py index 73e1cd4..abcdf4d 100644 --- a/src/connectrpc/_server_sync.py +++ b/src/connectrpc/_server_sync.py @@ -115,6 +115,22 @@ def prepare_response_headers( return headers +def _read_body_with_content_length( + environ: WSGIEnvironment, content_length: int +) -> bytes: + bytes_read = 0 + chunks = [] + input_stream: BytesIO = environ["wsgi.input"] + while bytes_read < content_length: + to_read = content_length - bytes_read + chunk = input_stream.read(to_read) + if not chunk: + break + chunks.append(chunk) + bytes_read += len(chunk) + return b"".join(chunks) + + def _read_body(environ: WSGIEnvironment) -> Iterator[bytes]: input_stream: BytesIO = environ["wsgi.input"] while True: @@ -257,7 +273,7 @@ def _handle_post_request( content_length = environ.get("CONTENT_LENGTH") content_length = 0 if not content_length else int(content_length) if content_length > 0: - req_body = environ["wsgi.input"].read(content_length) + req_body = _read_body_with_content_length(environ, content_length) else: req_body = b"".join(_read_body(environ)) From f5f1139485e0c765b54e60ff24bb2e81e4e5d63e Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 22 Oct 2025 10:15:43 +0900 Subject: [PATCH 2/2] Raise error on truncated and optimize for prebuffered Signed-off-by: Anuraag Agrawal --- src/connectrpc/_server_sync.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/connectrpc/_server_sync.py b/src/connectrpc/_server_sync.py index abcdf4d..c7aa3c8 100644 --- a/src/connectrpc/_server_sync.py +++ b/src/connectrpc/_server_sync.py @@ -118,9 +118,16 @@ def prepare_response_headers( def _read_body_with_content_length( environ: WSGIEnvironment, content_length: int ) -> bytes: - bytes_read = 0 - chunks = [] input_stream: BytesIO = environ["wsgi.input"] + + # Many app servers buffer the entire request before executing the app + # so do an optimistic read before looping. + chunk = input_stream.read(content_length) + if len(chunk) == content_length: + return chunk + + bytes_read = len(chunk) + chunks = [chunk] while bytes_read < content_length: to_read = content_length - bytes_read chunk = input_stream.read(to_read) @@ -128,6 +135,11 @@ def _read_body_with_content_length( break chunks.append(chunk) bytes_read += len(chunk) + if bytes_read < content_length: + raise ConnectError( + Code.INVALID_ARGUMENT, + f"request truncated, expected {content_length} bytes but only received {bytes_read} bytes", + ) return b"".join(chunks)