Repository navigation
Axum: enforce configurable buffered request-body limits #393
Description
Part of #391.
Description
Replace unlimited JSON request-body buffering in the Axum adapter with a configurable limit. Keep this issue focused on a small, testable safeguard with an established default; defer controls that need workload measurements or application-policy decisions.
Current implementation on audited main
JSON and application/*+json requests call to_bytes(axum_body, usize::MAX) before entering the core router. Core extractor checks cannot prevent this earlier allocation. Other request bodies remain streams.
Rationale for the default
Use 2 MiB, exactly 2,097,152 bytes, matching Axum's existing default for buffering extractors such as Bytes, String, Json, and Form. Our adapter's direct body collection bypasses that protection.
Sources:
- Axum DefaultBodyLimit documentation
- axum-core 0.5.6 request extension implementation, where
DEFAULT_LIMITis 2,097,152 bytes.
This is alignment with an established framework default, not a claim that 2 MiB suits every application or makes the whole process memory-safe. Applications must be able to configure a different limit. Fastly and Cloudflare resource quotas are not the basis for this value.
Prerequisite PR and scope correction
PR #275 is the prerequisite for this issue. It adds route-aware ingress admission, bounded body handling, request-start-relative read deadlines, application-owned grants, platform/memory metadata, and egress completion resources. The standard Axum path calls begin_ingress before body conversion and dispatch_admitted afterwards; see service.rs at the inspected PR head. Its request conversion keeps the body lazy and wraps reads in a deadline; see request.rs at the inspected PR head.
The old unlimited JSON prebuffer and per-request block_in_place bridge described in the main baseline are replaced by the PR. Do not fix those superseded paths independently. Re-audit the accepted version only for the remaining buffered-body limit, configuration override, 413 mapping, and conversion-owned cleanup. Reuse its existing bounded-body/admission mechanisms; active-request limits, handler cancellation, and general overload policy remain outside this issue.
Raw parser accounting and some memory/deadline guarantees are explicitly unsupported or BestEffort in the PR. Preserve those distinctions.
Proposed solution
- Inspect the accepted PR feat: implement portable outbound HTTP and request lifecycle contracts #275 request/body path and record which buffered JSON checks already exist. Do not reintroduce the retired eager prebuffer or enforce the same limit twice.
- Apply a configurable 2 MiB default to the buffered JSON/+json path through the accepted existing mechanism. Keep configuration validation and error mapping at the owning boundary.
- Enforce actual received bytes, not Content-Length alone. Prove exactly-at-limit success and one-byte-over HTTP 413 through the real service path.
- Stop consuming rejected/interrupted bodies and release conversion-owned resources using the existing lifecycle contract.
- Preserve incremental non-JSON handling. This issue adds no general stream-size/duration, concurrency, queue, or handler-deadline policy. Keep the explicitly deferred section below authoritative.
Done when
- Re-audit the accepted PR feat: implement portable outbound HTTP and request lifecycle contracts #275 implementation and record which checks are already covered. Add only residual changes and missing production acceptance evidence.
- The accepted JSON buffering path is bounded; if feat: implement portable outbound HTTP and request lifecycle contracts #275 removed the legacy eager collection, do not recreate it just to satisfy this check.
- The default is 2 MiB, with an explicit configuration override.
- The limit applies to both
application/jsonandapplication/*+json. - Enforcement checks bytes as they arrive, including chunked bodies without Content-Length.
- A body exactly at the limit is accepted; a body one byte beyond it receives HTTP 413 rather than 500.
- Tests cover declared-length and chunked bodies, the configured override, and interrupted/erroring reads. Verify oversized-body status through the adapter service path, not only a conversion helper.
- Rejected or failed collection stops consuming the body and releases conversion-owned resources.
- Non-JSON request bodies remain incremental, with no new total-size or duration restriction.
- Documentation explains that this bounds adapter-owned request-body buffering, not total process memory, handler allocations, or concurrency.
Explicitly deferred
These controls are not required to close this issue. Do not choose numerical defaults for them without the relevant evidence:
| Control | Evidence or decision needed |
|---|---|
| Maximum active requests | Load tests on a specified CPU/memory allocation with representative request costs |
| Body idle timeout | Expected client behavior, upload speeds, and reverse-proxy settings |
| Total upload deadline | Supported upload sizes and minimum acceptable throughput |
| Handler deadline | Application latency requirements, upstream budgets, and caller deadlines |
| Total streamed-body size | Application payload requirements, separate from retained-memory bounds |
| Maximum stream lifetime | Application support for SSE, long polling, and other long-lived responses |
| Application queue | A demonstrated benefit for the intended workload before introducing one |
General handler cancellation, overload protection, capacity recovery, and execution-bridge changes are also deferred from this narrowed issue. Cleanup here covers the body-conversion path only; it does not promise cancellation of arbitrary CPU-bound or blocking handler work.
Related work and boundaries
- Axum: preserve incremental streaming with bounded backpressure #394 owns streaming transport and backpressure. This narrowed issue is not a prerequisite for a general admission, deadline, or cancellation framework; Axum: preserve incremental streaming with bounded backpressure #394 uses the accepted feat: implement portable outbound HTTP and request lifecycle contracts #275 transport contracts.
- Axum: define a production server lifecycle with bounded graceful shutdown #392 owns server lifecycle and shutdown.
- Coordinate with open PR feat: implement portable outbound HTTP and request lifecycle contracts #275 if its ingress/request-conversion changes become the implementation baseline; avoid implementing competing limits for the same path.
- Keep changes in the Axum adapter, with a small shared error hook only if necessary. Do not add Tokio dependencies or blanket Send requirements to portable core types.
Affected areas
Adapter -- Axum; Documentation; Core only if a minimal shared error hook is necessary.
Dependencies
Primary prerequisite: PR #275. Build residual production work on its accepted contracts, not the superseded main implementation.
Implementation and relationship tracking
Implementation: draft PR #407 on issue-393-buffered-body-limit-spec, based on PR #406. This records the implementation branch order, not a new requirement that every #392/#396 acceptance item must finish before buffered-body work can proceed. Accepted-prerequisite integration and remaining acceptance are still required before merge. GitHub Development now links this issue to PR #407, whose description includes Closes #393. The issue remains open while the PR is draft.
Activity
[-]Axum: enforce request limits, deadlines, and overload protection[/-][+]Axum: enforce configurable buffered request-body limits[/+]