Skip to content

Commit affb46c

Browse files
committed
Harden the streaming publisher pipeline after review
Address the deep-review findings on the streaming cutover: - Cap cumulative decoded bytes in BodyStreamDecoder against publisher.max_buffered_body_bytes: the chunk source only bounds raw compressed bytes, so a decompression bomb could expand ~1000x past it and push unbounded decoded volume through the rewrite pipeline - Detect truncated deflate streams: write::ZlibDecoder::try_finish accepts truncated input silently, so the deflate arm now drives flate2::Decompress directly and requires Status::StreamEnd at finalization; trailing bytes after the end marker stay ignored. Add truncated-gzip and truncated-deflate regression tests - Make BodyChunkSource::next_chunk cancellation-safe by polling the body in place instead of moving it out across an await; a cancelled pull no longer turns into a silent EOF - Log dispatched auctions dropped uncollected (client disconnect mid-stream or never-polled body) via DispatchedAuctionGuard; the guard is created before the lazy stream so unpolled drops log too - Share the pull+decode step between the lazy publisher body and the write-sink drivers (hold_step_next_chunk / passthrough_step), removing the unreachable!() error plumbing and the triplicated processor selection; document body_close_hold_loop_stream as groundwork for the buffered adapters' streaming cutover - Pass identity-encoded chunks through zero-copy and finish encoders by consuming them instead of allocating a throwaway replacement - Add a Fastly dispatch test asserting the publisher fallback returns Body::Stream without a stale Content-Length, plus a comment on why the publisher fetch gates streaming on capability while the asset path does not Behavior note: gzip bodies with trailing garbage after the trailer now error mid-stream; the old read-path decoder ignored them.
1 parent 35e872b commit affb46c

4 files changed

Lines changed: 685 additions & 298 deletions

File tree

crates/trusted-server-adapter-fastly/src/app.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,6 +2150,10 @@ mod tests {
21502150

21512151
#[async_trait::async_trait(?Send)]
21522152
impl PlatformHttpClient for StreamingHttpClient {
2153+
fn supports_streaming_responses(&self) -> bool {
2154+
true
2155+
}
2156+
21532157
async fn send(
21542158
&self,
21552159
request: PlatformHttpRequest,
@@ -2260,6 +2264,36 @@ mod tests {
22602264
);
22612265
}
22622266

2267+
#[test]
2268+
fn dispatch_fallback_streams_publisher_body_without_buffering() {
2269+
// Regression guard for the publisher streaming cutover (#849): a
2270+
// successful publisher origin fetch must hand `edgezero_main` a lazy
2271+
// streaming body (`Body::Stream`) so headers commit at origin first
2272+
// byte, rather than draining the processed page into a buffered
2273+
// `Body::Once`. Core tests cover the rewrite pipeline itself; this
2274+
// guards the adapter wiring that could silently re-buffer.
2275+
let settings = test_settings();
2276+
let state = build_state_from_settings(settings).expect("should build state");
2277+
let services = streaming_runtime_services();
2278+
let req = empty_request(Method::GET, "/article");
2279+
2280+
let response = block_on(super::dispatch_fallback(&state, &services, req));
2281+
2282+
assert_eq!(
2283+
response.status(),
2284+
StatusCode::OK,
2285+
"publisher proxy should succeed against the streaming origin stub"
2286+
);
2287+
assert!(
2288+
matches!(response.body(), Body::Stream(_)),
2289+
"EdgeZero publisher dispatch must attach the lazy streaming body, not buffer it"
2290+
);
2291+
assert!(
2292+
!response.headers().contains_key(header::CONTENT_LENGTH),
2293+
"processed streaming publisher responses must not carry a stale Content-Length"
2294+
);
2295+
}
2296+
22632297
#[test]
22642298
fn dispatch_runs_request_filter_and_threads_response_effects() {
22652299
// Regression guard for the EdgeZero request-filter bypass: the publisher

0 commit comments

Comments
 (0)