This commit is contained in:
Mateusz Gruszczyński committed 2026-10-07 08:51:04 +02:00
1 parent a9f9450f80
commit 762eb9e28b
81 files changed
+1242 -1095

No files matched your search

+167 -76
View File
@@ -1,6 +1,7 @@
mod tests {
use super::*;
use http_body_util::BodyExt;
fn response() -> Response {
let mut response =
Response::new(Body::from("RustProxy compressible payload. ".repeat(500)));
@@ -12,6 +13,52 @@ mod tests {
.insert("etag", "\"origin\"".parse().unwrap());
response
}
#[test]
fn defaults_enable_modern_compression_without_deflate() {
let settings = Settings::default();
assert!(settings.zstd);
assert!(settings.br);
assert!(settings.gzip);
assert!(!settings.deflate);
assert!(settings.enabled());
}
#[test]
fn scoped_codec_overrides_keep_global_codecs_authoritative() {
let global = Settings::default();
assert!(!global.scoped(false).enabled());
let inherited = global.clone();
let selective = Overrides {
gzip: Some(false),
br: Some(true),
zstd: Some(false),
deflate: Some(true),
..Default::default()
}
.resolve(&global, &inherited);
assert!(!selective.gzip);
assert!(selective.br);
assert!(!selective.zstd);
assert!(!selective.deflate, "a scope cannot enable a globally disabled codec");
let disabled = Overrides { enabled: Some(false), ..Default::default() }
.resolve(&global, &inherited);
assert!(!disabled.enabled());
let parent_disabled = global.scoped(false);
let reenabled = Overrides {
enabled: Some(true),
gzip: Some(false),
..Default::default()
}
.resolve(&global, &parent_disabled);
assert!(reenabled.zstd && reenabled.br);
assert!(!reenabled.gzip);
assert_eq!(reenabled.min_bytes, global.min_bytes);
}
#[tokio::test]
async fn large_thresholds_do_not_truncate_or_panic() {
let mut h = HeaderMap::new();
@@ -19,8 +66,10 @@ mod tests {
for threshold in [65_535, 65_536, 1_048_576] {
let settings = Settings {
gzip: true,
br: false,
zstd: false,
deflate: false,
min_bytes: threshold,
..Default::default()
};
settings.validate().unwrap();
for (size, expected) in [(threshold - 1, false), (threshold, true)] {
@@ -38,9 +87,10 @@ mod tests {
}
}
}
#[tokio::test]
async fn negotiation_and_body_streaming() {
let s = Settings {
async fn negotiation_and_body_streaming_support_each_codec() {
let settings = Settings {
gzip: true,
br: true,
zstd: true,
@@ -48,10 +98,10 @@ mod tests {
..Default::default()
};
for encoding in ["gzip", "br", "zstd", "deflate"] {
let mut h = HeaderMap::new();
h.insert("accept-encoding", encoding.parse().unwrap());
let r = encode(response(), &h, &Method::GET, &s).await;
assert_eq!(r.headers()["content-encoding"], encoding);
let mut headers = HeaderMap::new();
headers.insert("accept-encoding", encoding.parse().unwrap());
let response = encode(response(), &headers, &Method::GET, &settings).await;
assert_eq!(response.headers()["content-encoding"], encoding);
let expected = match encoding {
"gzip" => AppliedEncoding::Gzip,
"br" => AppliedEncoding::Brotli,
@@ -60,26 +110,21 @@ mod tests {
_ => unreachable!(),
};
assert_eq!(
r.extensions().get::<AppliedEncoding>().copied(),
response.extensions().get::<AppliedEncoding>().copied(),
Some(expected)
);
assert_eq!(r.headers()["etag"], "W/\"origin\"");
assert!(r
assert_eq!(response.headers()["etag"], "W/\"origin\"");
assert!(response
.headers()
.get_all("vary")
.iter()
.any(|v| v.to_str().unwrap().contains("accept-encoding")));
assert!(r.into_body().collect().await.unwrap().to_bytes().len() < 1000);
assert!(response.into_body().collect().await.unwrap().to_bytes().len() < 1000);
}
let mut h = HeaderMap::new();
h.insert("accept-encoding", "gzip;q=0, identity;q=1".parse().unwrap());
assert!(!encode(response(), &h, &Method::GET, &s)
.await
.headers()
.contains_key("content-encoding"));
}
#[tokio::test]
async fn quality_weights_can_select_each_enabled_codec() {
async fn equal_quality_uses_zstd_then_br_then_gzip_then_deflate() {
let settings = Settings {
gzip: true,
br: true,
@@ -88,57 +133,77 @@ mod tests {
..Default::default()
};
let cases = [
(
"gzip;q=1, br;q=0.2, zstd;q=0.2, deflate;q=0.2",
"gzip",
AppliedEncoding::Gzip,
),
(
"gzip;q=0.2, br;q=1, zstd;q=0.2, deflate;q=0.2",
"br",
AppliedEncoding::Brotli,
),
(
"gzip;q=0.2, br;q=0.2, zstd;q=1, deflate;q=0.2",
"zstd",
AppliedEncoding::Zstd,
),
(
"gzip;q=0.2, br;q=0.2, zstd;q=0.2, deflate;q=1",
"deflate",
AppliedEncoding::Deflate,
),
("gzip, br, zstd, deflate", "zstd"),
("gzip, br, deflate", "br"),
("gzip, deflate", "gzip"),
("deflate", "deflate"),
("*", "zstd"),
];
for (accept, expected_header, expected_marker) in cases {
for (accept, expected) in cases {
let mut headers = HeaderMap::new();
headers.insert("accept-encoding", accept.parse().unwrap());
let response = encode(response(), &headers, &Method::GET, &settings).await;
assert_eq!(response.headers()["content-encoding"], expected_header);
assert_eq!(
response.extensions().get::<AppliedEncoding>().copied(),
Some(expected_marker),
"accept-encoding={accept}"
);
assert_eq!(response.headers()["content-encoding"], expected, "{accept}");
}
}
#[tokio::test]
async fn cache_safe_cookie_marker_allows_compression_without_weakening_default_guard() {
async fn quality_weights_win_before_server_priority() {
let settings = Settings {
gzip: true,
br: true,
zstd: true,
deflate: true,
..Default::default()
};
let cases = [
("gzip;q=1, br;q=.9, zstd;q=.8", "gzip"),
("gzip;q=.6, br;q=.9, zstd;q=.8", "br"),
("gzip;q=.6, br;q=.8, zstd;q=.9", "zstd"),
("gzip;q=0, br;q=0, zstd;q=0, deflate;q=1", "deflate"),
];
for (accept, expected) in cases {
let mut headers = HeaderMap::new();
headers.insert("accept-encoding", accept.parse().unwrap());
let response = encode(response(), &headers, &Method::GET, &settings).await;
assert_eq!(response.headers()["content-encoding"], expected, "{accept}");
}
let mut headers = HeaderMap::new();
headers.insert(
"accept-encoding",
"gzip;q=0, br;q=0, zstd;q=0, deflate;q=0, identity;q=1"
.parse()
.unwrap(),
);
assert!(!encode(response(), &headers, &Method::GET, &settings)
.await
.headers()
.contains_key("content-encoding"));
}
#[tokio::test]
async fn cookies_authorization_and_private_cache_policy_do_not_disable_compression() {
let settings = Settings {
gzip: true,
br: false,
zstd: false,
deflate: false,
..Default::default()
};
let mut headers = HeaderMap::new();
headers.insert("accept-encoding", "gzip".parse().unwrap());
headers.insert("cookie", "session=secret".parse().unwrap());
let blocked = encode(response(), &headers, &Method::GET, &settings).await;
assert!(!blocked.headers().contains_key("content-encoding"));
let mut safe = response();
safe.extensions_mut().insert(AllowCookie);
let encoded = encode(safe, &headers, &Method::GET, &settings).await;
headers.insert("authorization", "Bearer secret".parse().unwrap());
let mut dynamic = response();
dynamic
.headers_mut()
.insert("set-cookie", "session=next; HttpOnly".parse().unwrap());
dynamic.headers_mut().insert(
"cache-control",
"private, no-store".parse().unwrap(),
);
let encoded = encode(dynamic, &headers, &Method::GET, &settings).await;
assert_eq!(encoded.headers()["content-encoding"], "gzip");
}
@@ -146,6 +211,9 @@ mod tests {
async fn age_header_survives_downstream_compression() {
let settings = Settings {
gzip: true,
br: false,
zstd: false,
deflate: false,
..Default::default()
};
let mut headers = HeaderMap::new();
@@ -159,35 +227,58 @@ mod tests {
}
#[tokio::test]
async fn no_transform_personal_and_sse_are_not_compressed() {
let s = Settings {
async fn existing_content_encoding_is_never_recompressed() {
let settings = Settings::default();
let mut headers = HeaderMap::new();
headers.insert("accept-encoding", "zstd, br, gzip".parse().unwrap());
let mut upstream = response();
upstream
.headers_mut()
.insert("content-encoding", "gzip".parse().unwrap());
let encoded = encode(upstream, &headers, &Method::GET, &settings).await;
assert_eq!(encoded.headers()["content-encoding"], "gzip");
assert!(encoded.extensions().get::<AppliedEncoding>().is_none());
}
#[tokio::test]
async fn no_transform_range_and_sse_are_not_compressed() {
let settings = Settings {
gzip: true,
br: false,
zstd: false,
deflate: false,
..Default::default()
};
let mut h = HeaderMap::new();
h.insert("accept-encoding", "gzip".parse().unwrap());
for name in ["authorization", "cookie", "range"] {
let mut private = h.clone();
private.insert(
http::HeaderName::from_bytes(name.as_bytes()).unwrap(),
"value".parse().unwrap(),
);
assert!(!encode(response(), &private, &Method::GET, &s)
.await
.headers()
.contains_key("content-encoding"));
}
let mut r = response();
r.headers_mut()
.insert("cache-control", "no-transform".parse().unwrap());
assert!(!encode(r, &h, &Method::GET, &s)
let mut headers = HeaderMap::new();
headers.insert("accept-encoding", "gzip".parse().unwrap());
let mut ranged = headers.clone();
ranged.insert("range", "bytes=0-99".parse().unwrap());
assert!(!encode(response(), &ranged, &Method::GET, &settings)
.await
.headers()
.contains_key("content-encoding"));
let mut r = response();
r.headers_mut()
let mut no_transform_request = headers.clone();
no_transform_request.insert("cache-control", "no-transform".parse().unwrap());
assert!(!encode(response(), &no_transform_request, &Method::GET, &settings)
.await
.headers()
.contains_key("content-encoding"));
let mut no_transform_response = response();
no_transform_response
.headers_mut()
.insert("cache-control", "public, no-transform".parse().unwrap());
assert!(!encode(no_transform_response, &headers, &Method::GET, &settings)
.await
.headers()
.contains_key("content-encoding"));
let mut sse = response();
sse.headers_mut()
.insert("content-type", "text/event-stream".parse().unwrap());
assert!(!encode(r, &h, &Method::GET, &s)
assert!(!encode(sse, &headers, &Method::GET, &settings)
.await
.headers()
.contains_key("content-encoding"));