Skip to content

Commit ef576a1

Browse files
committed
Preserve Content-Length for PassThrough by using send_to_client
PassThrough reattaches the unmodified body and uses send_to_client() instead of stream_to_client() + io::copy. This preserves Content-Length (avoids chunked encoding overhead for images/fonts) and lets Fastly stream from its internal buffer without WASM memory buffering.
1 parent c3f8610 commit ef576a1

2 files changed

Lines changed: 25 additions & 29 deletions

File tree

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,6 @@ async fn route_request(
238238
// Response already sent via stream_to_client()
239239
return None;
240240
}
241-
Ok(PublisherResponse::Buffered(response)) => Ok(response),
242-
Err(e) => {
243-
log::error!("Failed to proxy to publisher origin: {:?}", e);
244-
Err(e)
245-
}
246-
}
247241
Ok(PublisherResponse::PassThrough {
248242
mut response,
249243
body,

crates/trusted-server-core/src/publisher.rs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -276,12 +276,13 @@ pub enum PublisherResponse {
276276
},
277277
/// Non-processable 2xx response (images, fonts, video). The caller must:
278278
/// 1. Call `finalize_response()` on the response
279-
/// 2. Call `response.stream_to_client()` to get a `StreamingBody`
280-
/// 3. Copy body bytes directly via `io::copy(&mut body, &mut streaming_body)`
281-
/// 4. Call `StreamingBody::finish()`
279+
/// 2. Reattach the body via `response.set_body(body)`
280+
/// 3. Call `response.send_to_client()`
282281
///
283-
/// `Content-Length` is removed because `stream_to_client()` uses chunked
284-
/// transfer encoding. The body content is unmodified.
282+
/// `Content-Length` is preserved — the body is unmodified. Using
283+
/// `send_to_client()` instead of `stream_to_client()` avoids chunked
284+
/// encoding overhead. Fastly streams the body from its internal buffer
285+
/// without copying into WASM memory.
285286
PassThrough {
286287
/// Response with all headers set but body not yet written.
287288
response: Response,
@@ -462,12 +463,12 @@ pub fn handle_publisher_request(
462463

463464
// Stream non-processable 2xx responses directly to avoid buffering
464465
// large binaries (images, fonts, video) in memory.
465-
// Exclude 204 No Content — it must not have a message body, and
466-
// stream_to_client() would add chunked Transfer-Encoding.
466+
// Content-Length is preserved — the body is unmodified, so the
467+
// browser knows the exact size for progress/layout.
468+
// Exclude 204 No Content — it must not have a message body.
467469
let status = response.get_status();
468470
if status.is_success() && status != StatusCode::NO_CONTENT && !should_process {
469471
let body = response.take_body();
470-
response.remove_header(header::CONTENT_LENGTH);
471472
return Ok(PublisherResponse::PassThrough { response, body });
472473
}
473474

@@ -830,33 +831,34 @@ mod tests {
830831
}
831832

832833
#[test]
833-
fn pass_through_preserves_body_and_removes_content_length() {
834-
// Simulate the PassThrough path: take body, remove Content-Length,
835-
// io::copy to output. Verify byte-for-byte identity.
834+
fn pass_through_preserves_body_and_content_length() {
835+
// Simulate the PassThrough path: take body, reattach, send.
836+
// Verify byte-for-byte identity and Content-Length preservation.
836837
let image_bytes: Vec<u8> = (0..=255).cycle().take(4096).collect();
837838

838839
let mut response = Response::from_status(StatusCode::OK);
839840
response.set_header("content-type", "image/png");
840841
response.set_header("content-length", image_bytes.len().to_string());
841842
response.set_body(Body::from(image_bytes.clone()));
842843

843-
// Simulate PassThrough: take body, remove Content-Length
844-
// (stream_to_client uses chunked encoding)
845-
let mut body = response.take_body();
846-
response.remove_header(header::CONTENT_LENGTH);
847-
848-
// io::copy into a Vec (simulating StreamingBody)
849-
let mut output = Vec::new();
850-
std::io::copy(&mut body, &mut output).expect("should copy body");
844+
// Simulate PassThrough: take body then reattach
845+
let body = response.take_body();
846+
// Body is unmodified — Content-Length stays correct
847+
assert_eq!(
848+
response
849+
.get_header_str("content-length")
850+
.expect("should have content-length"),
851+
"4096",
852+
"Content-Length should be preserved for pass-through"
853+
);
851854

855+
// Reattach and verify body content
856+
response.set_body(body);
857+
let output = response.into_body().into_bytes();
852858
assert_eq!(
853859
output, image_bytes,
854860
"pass-through should preserve body byte-for-byte"
855861
);
856-
assert!(
857-
response.get_header(header::CONTENT_LENGTH).is_none(),
858-
"Content-Length should be removed for streaming pass-through"
859-
);
860862
}
861863

862864
#[test]

0 commit comments

Comments
 (0)