Skip to content

Stream EdgeZero asset responses instead of buffering them#825

Open
aram356 wants to merge 2 commits into
mainfrom
stream-edgezero-asset-responses
Open

Stream EdgeZero asset responses instead of buffering them#825
aram356 wants to merge 2 commits into
mainfrom
stream-edgezero-asset-responses

Conversation

@aram356

@aram356 aram356 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

On the EdgeZero entry path, asset-route responses (including Fastly Image Optimizer output) were fully read into the Wasm heap before being sent, instead of being streamed to the client. This streams them, matching the legacy path.

handle_asset_proxy_request already requests a streaming origin response (with_stream_response) and returns a Body::Stream. The EdgeZero dispatcher was discarding that: dispatch_asset_fallback drained the stream via buffer_asset_body (a BoundedWriter capped by publisher.max_buffered_body_bytes) and re-attached the bytes as a buffered Body::Once, then edgezero_main sent it through the buffered-only compat::to_fastly_response(...).send_to_client(). This was a deliberate interim during the dual-path cutover.

The legacy main.rs path never buffered — its HandlerOutcome::AssetStreaming arm commits headers with stream_to_client() and pipes the body chunk-by-chunk via stream_asset_body. This PR wires the same seam into the EdgeZero path.

Changes

  • dispatch_asset_fallback attaches the origin Body::Stream to the response instead of buffering. HEAD and bodiless statuses (204, 304) drop the stream so they keep the origin Content-Length and carry no body.
  • edgezero_main now sends through a new send_core_response helper: Body::Stream responses are committed header-first via stream_to_client() and piped with stream_asset_body; buffered Body::Once responses are sent in one shot. Both send sites use it.
  • Removed the now-dead buffer_asset_body / BoundedWriter usage and refreshed the stale buffering docs.

Publisher (HTML) responses remain buffered — unchanged.

Why no edgezero change

edgezero_core::body::Body already carries a Stream variant, and the buffering lived entirely in trusted-server-adapter-fastly. The dependency stays pinned to edgezero@branch=main; this is not part of edgezero PR #269 (the CLI-extensions epic), which is unrelated scope.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace (adapter 107 passed incl. the new test; workspace 1420+ passed, 0 failed)
  • cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • New regression test dispatch_asset_fallback_streams_origin_body_without_buffering injects a streaming HTTP client and asserts the dispatched asset response body is Body::Stream.

Note: the end-to-end stream_to_client() send seam in send_core_response requires the Fastly Compute runtime and is covered the same way the legacy AssetStreaming path is — the chunk-piping primitive stream_asset_body has its own unit test in core (stream_asset_body_reports_mid_stream_origin_errors).

Closes #826.
Part of #480.

The EdgeZero entry point drained every asset/image-optimizer response into
the Wasm heap before sending: `dispatch_asset_fallback` ran `buffer_asset_body`
(a `BoundedWriter` capped by `publisher.max_buffered_body_bytes`) and re-attached
the bytes as a buffered `Body::Once`. This defeated the streaming that
`handle_asset_proxy_request` already produces (it requests a streaming origin
response and hands back a `Body::Stream`), so large optimized images were fully
materialized in memory.

The legacy `main.rs` path never buffered — its `HandlerOutcome::AssetStreaming`
arm commits headers via `stream_to_client()` and pipes the body chunk-by-chunk
with `stream_asset_body`. Wire the same seam into the EdgeZero path:

- `dispatch_asset_fallback` now attaches the origin `Body::Stream` to the
  response (dropping it only for HEAD/204/304, which keep the origin
  Content-Length and carry no body) instead of buffering.
- `edgezero_main` sends through a new `send_core_response` helper that streams
  `Body::Stream` responses with `stream_to_client()` + `stream_asset_body` and
  sends buffered `Body::Once` responses in one shot.
- Remove the now-dead `buffer_asset_body`/`BoundedWriter` usage and refresh the
  stale buffering docs.

No edgezero dependency change is needed: `edgezero_core::body::Body` already
carries a `Stream` variant. Publisher responses remain buffered (unchanged).

Adds `dispatch_asset_fallback_streams_origin_body_without_buffering`, which
injects a streaming HTTP client and asserts the dispatched response body is
`Body::Stream`.
@aram356 aram356 self-assigned this Jun 26, 2026
@aram356 aram356 requested a review from jevansnyc June 26, 2026 15:50
@aram356 aram356 removed the request for review from jevansnyc June 26, 2026 15:51
@ChristianPavilonis

Copy link
Copy Markdown
Collaborator

tested on staging, didn't get any 502 errors.

@ChristianPavilonis ChristianPavilonis marked this pull request as ready for review June 26, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EdgeZero path buffers asset/image-optimizer responses instead of streaming them

2 participants