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.
This commit is contained in:
Benoit Chesneau 2026-03-22 00:00:37 +01:00
parent 464cbbfad5
commit ea37eaaa6d

View File

@ -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.