Skip to content

Streaming request body #26

Description

@EdmondDantes

Replaces the buffer-the-whole-body-then-dispatch-handler path with an optional pull-based streaming API. Unblocks HttpArena upload profile (currently ~20 GiB peak at 1000 concurrent 20 MiB uploads) and enables proxy / direct-to-storage / incremental-hash use cases.

Full design lives in docs/PLAN_STREAMING_INGRESS.md. Tracking issue body summarizes the contract.

Userland API

TrueAsync\HttpRequest:

  • readBody(int \$maxLen = 65536): ?string — null = EOF
  • readBodyChunks(int \$maxLen = 65536): ?array — zero-copy variant

TrueAsync\HttpServerConfig:

  • setMaxBodyQueueBytes(int): static — per-stream watermark, default 256 KiB

Mode trichotomy (first call wins, mismatched calls throw LogicException):

first call mode
getBody() / awaitBody() buffered (current behavior)
readBody*() streaming
getFiles() / getPost() multipart-parsed

Internals

  • Per-request zend_async_channel_t (ZEND_ASYNC_NEW_CHANNEL(16, false, false)) — backed by existing circular_buffer in ext-async, awaitable for free.
  • Double byte limit: per-stream watermark (backpressure) + cumulative max_body_size (413).
  • Per-transport backpressure:
    • H1: llhttp_pause / llhttp_resume with 50% low-water threshold (anti-chatter)
    • H2: defer nghttp2_session_consume until drain; advertise SETTINGS_MAX_FRAME_SIZE = watermark
    • H3: defer ngtcp2_conn_extend_max_stream_offset; initial_max_stream_data sized to watermark

Expected impact

20 MiB upload × 1000 concurrent streams:

  • before: ~20 GiB peak RSS
  • after: ~320 MiB peak RSS — 60× reduction

For handlers that keep calling getBody() — zero overhead added.

PR breakdown

PR Scope Blocks
#1 setMaxBodyQueueBytes + plumbing + PHPT —
#2 http_request_t body-mode trichotomy; lazy multipart activation #1
#3 readBody() + H1 integration #2
#4 H2 integration + SETTINGS_MAX_FRAME_SIZE #3
#5 H3 integration #3
#6 readBodyChunks() #3
#7 HttpArena entry.php rewrite for /upload + RSS measurement #3–#6

PRs #4 and #5 are independent and can land in either order.

Out of scope

Streaming response body (already shipped — see commit history), iterator/generator wrapper, PSR-7 StreamInterface wrapper, C-level direct channel consumers, per-connection aggregate watermark.

See docs/PLAN_STREAMING_INGRESS.md §5 for full edge-case matrix and §6 for performance notes.

Activity

  1. changed the title [-]Streaming request body — design + plan (PLAN_STREAMING_INGRESS.md)[/-] [+]Streaming request body[/+] on May 16, 2026
  2. self-assigned this
    on May 16, 2026
  3. added this to the Next milestone on May 16, 2026
  4. linked a pull request that will close this issue26 streaming request body #27on May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions