v0.27.0
This commit is contained in:
1 parent
a9f9450f80
commit
762eb9e28b
81 files changed
+1242
-1095
No files matched your search
+167
-76
@@ -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"));
|
||||
|
||||
Reference in new issue
Block a user