Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skip compression for range requests #446

Merged
merged 5 commits into from
Jan 26, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tower-http/src/compression/future.rs
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ where

// never recompress responses that are already compressed
let should_compress = !res.headers().contains_key(header::CONTENT_ENCODING)
&& !res.headers().contains_key(header::CONTENT_RANGE)
&& self.predicate.should_compress(&res);

let (mut parts, body) = res.into_parts();
@@ -94,6 +95,7 @@ where
}
};

parts.headers.remove(header::ACCEPT_RANGES);
parts.headers.remove(header::CONTENT_LENGTH);

parts
66 changes: 65 additions & 1 deletion tower-http/src/compression/mod.rs
Original file line number Diff line number Diff line change
@@ -95,7 +95,9 @@ mod tests {
use crate::test_helpers::{Body, WithTrailers};
use async_compression::tokio::write::{BrotliDecoder, BrotliEncoder};
use flate2::read::GzDecoder;
use http::header::{ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_TYPE};
use http::header::{
ACCEPT_ENCODING, ACCEPT_RANGES, CONTENT_ENCODING, CONTENT_RANGE, CONTENT_TYPE, RANGE,
};
use http::{HeaderMap, HeaderName, Request, Response};
use http_body_util::BodyExt;
use std::convert::Infallible;
@@ -391,4 +393,66 @@ mod tests {
"Compression level is not respected"
);
}

#[tokio::test]
async fn should_not_compress_ranges() {
let svc = service_fn(|_| async {
let mut res = Response::new(Body::from("Hello"));
let headers = res.headers_mut();
headers.insert(ACCEPT_RANGES, "bytes".parse().unwrap());
headers.insert(CONTENT_RANGE, "bytes 0-4/*".parse().unwrap());
Ok::<_, std::io::Error>(res)
});
let mut svc = Compression::new(svc).compress_when(Always);

// call the service
let req = Request::builder()
.header(ACCEPT_ENCODING, "gzip")
.header(RANGE, "bytes=0-4")
.body(Body::empty())
.unwrap();
let res = svc.ready().await.unwrap().call(req).await.unwrap();
let headers = res.headers().clone();

// read the uncompressed body
let collected = res.into_body().collect().await.unwrap().to_bytes();

assert_eq!(headers[ACCEPT_RANGES], "bytes");
assert!(!headers.contains_key(CONTENT_ENCODING));
assert_eq!(collected, "Hello");
}

#[tokio::test]
async fn should_strip_accept_ranges_header_when_compressing() {
let svc = service_fn(|_| async {
let mut res = Response::new(Body::from("Hello, World!"));
res.headers_mut()
.insert(ACCEPT_RANGES, "bytes".parse().unwrap());
Ok::<_, std::io::Error>(res)
});
let mut svc = Compression::new(svc).compress_when(Always);

// call the service
let req = Request::builder()
.header(ACCEPT_ENCODING, "gzip")
.body(Body::empty())
.unwrap();
let res = svc.ready().await.unwrap().call(req).await.unwrap();
let headers = res.headers().clone();

// read the compressed body
let collected = res.into_body().collect().await.unwrap();
let compressed_data = collected.to_bytes();

// decompress the body
// doing this with flate2 as that is much easier than async-compression and blocking during
// tests is fine
let mut decoder = GzDecoder::new(&compressed_data[..]);
let mut decompressed = String::new();
decoder.read_to_string(&mut decompressed).unwrap();

assert!(!headers.contains_key(ACCEPT_RANGES));
assert_eq!(headers[CONTENT_ENCODING], "gzip");
assert_eq!(decompressed, "Hello, World!");
}
}