Skip to content

Add opt-in request body passthrough to avoid buffering large uploads #8483

Description

@emmanuelchaplais2

Is your feature request related to a problem? Please describe.

Request bodies are currently buffered at three sites in the Gateway's request path: the muxer (pre-routing) and the reverse proxy (after routing). This buffering multiplies memory consumption for large uploads.

In production, a 1 GiB chunked POST (e.g., file upload with chunked transfer encoding) causes a peak RSS delta of 2.28 GiB across the three buffering sites, despite the body flowing unchanged to the upstream. For streaming-capable upstreams (S3, file servers, media ingest), the buffering provides no value—it only delays upload completion and taxes memory on the Gateway.

Prior work acknowledged this issue (#2357, #7754, TT-5186 / #3976, TT-14016 / #8150): TT-5186 proposed a streaming path but was closed without implementation; #8150 documented the memory multiplication but did not ship a configurable opt-in.

Describe the solution you'd like

Add a default-off, two-layer opt-in for request body passthrough:

  1. Gateway-wide gate: new field http_server_options.enable_request_body_passthrough (boolean, defaults to false). Controls buffering at the muxer (pre-routing sites).
  2. Per-API flag: new field server.requestBodyPassthrough.enabled in OAS (or enable_request_body_passthrough in classic API definitions). Controls redundant copying at the reverse proxy (post-routing site).

When both are true for a request, the body streams directly to the upstream with no intermediate buffering. Requests where only one is true degrade gracefully to today's buffered behavior (safe, no regression).

Approach & Design Rationale:

  • Muxer buffering is unavoidable when the per-API flag is unknown (pre-routing has no APISpec). The gateway-wide gate gates both muxer sites unconditionally.
  • Reverse proxy buffering (deepCopyBody) runs after routing, so it can see the per-API flag independently. It skips only when that flag is true, improving memory even if the gateway-wide gate is off.
  • Both gates must be true for end-to-end streaming, preventing accidental exposure on non-opted APIs while allowing each layer to improve independently.
  • The implementation is config-gated (no mutable hook variables), non-panicking (plain bool reads), and compatible with existing middleware and size limits.

Describe alternatives you've considered

  1. Per-API-only gate (no gateway-wide toggle): Muxer buffering happens regardless, so memory only improves at the reverse proxy. Limits the win to ~33% instead of 100%. Rejected because the muxer is the primary multiplier (greedy full read in nopCloseRequestBodyErr).

  2. Labs config entry: Defers maturity feedback to after merge. Rejected because the design is straightforward and test coverage is comprehensive; a labs entry adds no safety beyond the two-layer default-off structure.

  3. Hook variable (SeqOne's internal approach): Allows runtime override without restart. Rejected because it requires a mutable global state and panic-safety wrapper; config-driven opt-in aligns with Tyk's established patterns (Add per-API option to disable upstream chunked transfer encoding by buffering in Tyk (with safe defaults) #7754, labs map) and avoids the complexity.

Additional context

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions