From ea37eaaa6d80bc096d22403095496ac657eedb00 Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Sun, 22 Mar 2026 00:00:37 +0100 Subject: [PATCH] Add streaming body support to HTTP2Stream - Add _body_chunks, _body_event, _body_complete fields for streaming - Modify receive_data() to populate chunks queue alongside BytesIO - Add async read_body_chunk() method for streaming body reads This enables HTTP/2 request body streaming instead of buffering entire uploads, reducing memory usage for large file uploads. --- gunicorn/http2/stream.py | 42 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/gunicorn/http2/stream.py b/gunicorn/http2/stream.py index 18ba40f4..8d03fdaf 100644 --- a/gunicorn/http2/stream.py +++ b/gunicorn/http2/stream.py @@ -71,6 +71,11 @@ class HTTP2Stream: self.priority_depends_on = 0 self.priority_exclusive = False + # Streaming body support (avoids buffering entire uploads) + self._body_chunks = [] + self._body_event = None # Lazy-init asyncio.Event + self._body_complete = False + @property def is_client_stream(self): """Check if this is a client-initiated stream (odd stream ID).""" @@ -122,7 +127,7 @@ class HTTP2Stream: self.request_complete = True def receive_data(self, data, end_stream=False): - """Process received DATA frame. + """Process received DATA frame with streaming support. Args: data: Bytes received @@ -137,11 +142,21 @@ class HTTP2Stream: f"Cannot receive data in state {self.state.name}" ) + # Add to chunks queue for streaming reads + if data: + self._body_chunks.append(data) + if self._body_event: + self._body_event.set() + + # Also write to legacy BytesIO for compatibility self.request_body.write(data) if end_stream: self._half_close_remote() self.request_complete = True + self._body_complete = True + if self._body_event: + self._body_event.set() def receive_trailers(self, trailers): """Process received trailing headers. @@ -283,6 +298,31 @@ class HTTP2Stream: """ return self.request_body.getvalue() + async def read_body_chunk(self): + """Read next body chunk asynchronously for streaming. + + Returns: + bytes: Next chunk of body data, or None if body is complete. + """ + import asyncio + + # Initialize event lazily (avoids event loop issues at construction) + if self._body_event is None: + self._body_event = asyncio.Event() + + while True: + # Return chunk if available + if self._body_chunks: + return self._body_chunks.pop(0) + + # No more data expected + if self._body_complete: + return None + + # Wait for more data + self._body_event.clear() + await self._body_event.wait() + def get_pseudo_headers(self): """Extract HTTP/2 pseudo-headers from request headers.