From eeda42e8d36f432feb4de8a14ae0592a987d7fd1 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Sat, 8 Mar 2025 13:38:26 +0800 Subject: [PATCH 1/5] feat(decompression): support HTTP responses containing multiple ZSTD frames --- tower-http/src/decompression/body.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tower-http/src/decompression/body.rs b/tower-http/src/decompression/body.rs index 9378e5ca..a35036de 100644 --- a/tower-http/src/decompression/body.rs +++ b/tower-http/src/decompression/body.rs @@ -397,7 +397,9 @@ where type Output = ZstdDecoder; fn apply(input: Self::Input, _quality: CompressionLevel) -> Self::Output { - ZstdDecoder::new(input) + let decoder = ZstdDecoder::new(input); + decoder.multiple_members(true); + decoder } fn get_pin_mut(pinned: Pin<&mut Self::Output>) -> Pin<&mut Self::Input> { From 21828b25fb2e3a8a64840cee97e42f99cfc6ac17 Mon Sep 17 00:00:00 2001 From: gngpp Date: Sat, 8 Mar 2025 13:56:01 +0800 Subject: [PATCH 2/5] feat(decompression): support HTTP responses containing multiple ZSTD frames --- tower-http/src/decompression/body.rs | 2 +- tower-http/src/decompression/mod.rs | 34 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tower-http/src/decompression/body.rs b/tower-http/src/decompression/body.rs index a35036de..a2970d65 100644 --- a/tower-http/src/decompression/body.rs +++ b/tower-http/src/decompression/body.rs @@ -397,7 +397,7 @@ where type Output = ZstdDecoder; fn apply(input: Self::Input, _quality: CompressionLevel) -> Self::Output { - let decoder = ZstdDecoder::new(input); + let mut decoder = ZstdDecoder::new(input); decoder.multiple_members(true); decoder } diff --git a/tower-http/src/decompression/mod.rs b/tower-http/src/decompression/mod.rs index 708df439..50d4d5fa 100644 --- a/tower-http/src/decompression/mod.rs +++ b/tower-http/src/decompression/mod.rs @@ -168,6 +168,24 @@ mod tests { assert_eq!(decompressed_data, "Hello, World!"); } + #[tokio::test] + async fn decompress_multi_zstd() { + let mut client = Decompression::new(service_fn(handle_multi_zstd)); + + let req = Request::builder() + .header("accept-encoding", "zstd") + .body(Body::empty()) + .unwrap(); + let res = client.ready().await.unwrap().call(req).await.unwrap(); + + // read the body, it will be decompressed automatically + let body = res.into_body(); + let decompressed_data = + String::from_utf8(body.collect().await.unwrap().to_bytes().to_vec()).unwrap(); + + assert_eq!(decompressed_data, "Hello, World!"); + } + async fn handle_multi_gz(_req: Request) -> Result, Infallible> { let mut buf = Vec::new(); let mut enc1 = GzEncoder::new(&mut buf, Default::default()); @@ -184,6 +202,22 @@ mod tests { Ok(res) } + async fn handle_multi_zstd(_req: Request) -> Result, Infallible> { + let mut buf = Vec::new(); + let mut enc1 = zstd::Encoder::new(&mut buf, Default::default()).unwrap(); + enc1.write_all(b"Hello, ").unwrap(); + enc1.finish().unwrap(); + + let mut enc2 = zstd::Encoder::new(&mut buf, Default::default()).unwrap(); + enc2.write_all(b"World!").unwrap(); + enc2.finish().unwrap(); + + let mut res = Response::new(Body::from(buf)); + res.headers_mut() + .insert("content-encoding", "zstd".parse().unwrap()); + Ok(res) + } + #[allow(dead_code)] async fn is_compatible_with_hyper() { let client = From de7eec854cffc0b8bbbd873a28f9616d1b135954 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 6 Jun 2025 09:20:34 +0800 Subject: [PATCH 3/5] feat(follow_redirect): Preserve extensions during redirection --- tower-http/src/follow_redirect/mod.rs | 31 +++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/tower-http/src/follow_redirect/mod.rs b/tower-http/src/follow_redirect/mod.rs index a90e0825..0c57764c 100644 --- a/tower-http/src/follow_redirect/mod.rs +++ b/tower-http/src/follow_redirect/mod.rs @@ -6,8 +6,7 @@ //! redirections. //! //! The middleware tries to clone the original [`Request`] when making a redirected request. -//! However, since [`Extensions`][http::Extensions] are `!Clone`, any extensions set by outer -//! middleware will be discarded. Also, the request body cannot always be cloned. When the +//! Also, the request body cannot always be cloned. When the //! original body is known to be empty by [`Body::size_hint`], the middleware uses `Default` //! implementation of the body type to create a new request body. If you know that the body can be //! cloned in some way, you can tell the middleware to clone it by configuring a [`policy`]. @@ -98,8 +97,8 @@ use self::policy::{Action, Attempt, Policy, Standard}; use futures_util::future::Either; use http::{ header::CONTENT_ENCODING, header::CONTENT_LENGTH, header::CONTENT_TYPE, header::LOCATION, - header::TRANSFER_ENCODING, HeaderMap, HeaderValue, Method, Request, Response, StatusCode, Uri, - Version, + header::TRANSFER_ENCODING, Extensions, HeaderMap, HeaderValue, Method, Request, Response, + StatusCode, Uri, Version, }; use http_body::Body; use iri_string::types::{UriAbsoluteString, UriReferenceStr}; @@ -219,6 +218,7 @@ where uri: req.uri().clone(), version: req.version(), headers: req.headers().clone(), + extensions: req.extensions().clone(), body, future: Either::Left(service.call(req)), service, @@ -242,6 +242,7 @@ pin_project! { uri: Uri, version: Version, headers: HeaderMap, + extensions: Extensions, body: BodyRepr, } } @@ -322,6 +323,7 @@ where *req.method_mut() = this.method.clone(); *req.version_mut() = *this.version; *req.headers_mut() = this.headers.clone(); + *req.extensions_mut() = this.extensions.clone(); this.policy.on_request(&mut req); this.future .set(Either::Right(Oneshot::new(this.service.clone(), req))); @@ -461,6 +463,27 @@ mod tests { ); } + #[tokio::test] + async fn tracing_extension() { + let svc = ServiceBuilder::new() + .layer(FollowRedirectLayer::with_policy(Action::Follow)) + .buffer(1) + .service_fn(handle); + let mut builder = Request::builder().uri("http://example.com/42"); + + builder + .extensions_mut() + .unwrap() + .insert(RequestUri(Uri::from_static("http://example.com/42"))); + let req = builder.body(Body::empty()).unwrap(); + let res = svc.oneshot(req).await.unwrap(); + assert_eq!(*res.body(), 0); + assert_eq!( + res.extensions().get::().unwrap().0, + "http://example.com/0" + ); + } + /// A server with an endpoint `GET /{n}` which redirects to `/{n-1}` unless `n` equals zero, /// returning `n` as the response body. async fn handle(req: Request) -> Result, Infallible> { From f889e94c31de7223e32881250e473b0f3e53956d Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 6 Jun 2025 09:31:11 +0800 Subject: [PATCH 4/5] feat(follow_redirect): Preserve extensions during redirection --- tower-http/src/follow_redirect/mod.rs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tower-http/src/follow_redirect/mod.rs b/tower-http/src/follow_redirect/mod.rs index 0c57764c..d279d3db 100644 --- a/tower-http/src/follow_redirect/mod.rs +++ b/tower-http/src/follow_redirect/mod.rs @@ -463,27 +463,6 @@ mod tests { ); } - #[tokio::test] - async fn tracing_extension() { - let svc = ServiceBuilder::new() - .layer(FollowRedirectLayer::with_policy(Action::Follow)) - .buffer(1) - .service_fn(handle); - let mut builder = Request::builder().uri("http://example.com/42"); - - builder - .extensions_mut() - .unwrap() - .insert(RequestUri(Uri::from_static("http://example.com/42"))); - let req = builder.body(Body::empty()).unwrap(); - let res = svc.oneshot(req).await.unwrap(); - assert_eq!(*res.body(), 0); - assert_eq!( - res.extensions().get::().unwrap().0, - "http://example.com/0" - ); - } - /// A server with an endpoint `GET /{n}` which redirects to `/{n-1}` unless `n` equals zero, /// returning `n` as the response body. async fn handle(req: Request) -> Result, Infallible> { From db0c9a1c31b8685e5ce2fe38f0e8ff19756f4492 Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 6 Jun 2025 22:49:53 +0800 Subject: [PATCH 5/5] Update mod.rs Co-authored-by: Daiki Mizukami --- tower-http/src/follow_redirect/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tower-http/src/follow_redirect/mod.rs b/tower-http/src/follow_redirect/mod.rs index d279d3db..8c87285c 100644 --- a/tower-http/src/follow_redirect/mod.rs +++ b/tower-http/src/follow_redirect/mod.rs @@ -6,7 +6,7 @@ //! redirections. //! //! The middleware tries to clone the original [`Request`] when making a redirected request. -//! Also, the request body cannot always be cloned. When the +//! However, the request body cannot always be cloned. When the //! original body is known to be empty by [`Body::size_hint`], the middleware uses `Default` //! implementation of the body type to create a new request body. If you know that the body can be //! cloned in some way, you can tell the middleware to clone it by configuring a [`policy`].