v0.25.5
This commit is contained in:
1 parent
1dea7f55c8
commit
c819f0a335
151 files changed
+7610
-4264
No files matched your search
@@ -29,7 +29,8 @@ pub async fn apply(
|
||||
if response.headers().contains_key("content-range") {
|
||||
return Ok(skip(response, None, "content_range"));
|
||||
}
|
||||
let Some(kind) = content_type_kind(response.headers()).or_else(|| extension_kind(path, settings))
|
||||
let Some(kind) =
|
||||
content_type_kind(response.headers()).or_else(|| extension_kind(path, settings))
|
||||
else {
|
||||
return Ok(skip(response, None, "unsupported_type"));
|
||||
};
|
||||
@@ -79,9 +80,7 @@ pub async fn apply(
|
||||
.collect()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"collect bounded static response for transformation: {error}"
|
||||
)
|
||||
anyhow::anyhow!("collect bounded static response for transformation: {error}")
|
||||
})?
|
||||
.to_bytes();
|
||||
if raw.len() > settings.max_body_bytes {
|
||||
|
||||
+25
-38
@@ -68,7 +68,9 @@ fn content_type_kind(headers: &HeaderMap) -> Option<Kind> {
|
||||
let value = headers.get("content-type")?.to_str().ok()?;
|
||||
let mime = value.split(';').next()?.trim().to_ascii_lowercase();
|
||||
match mime.as_str() {
|
||||
"application/javascript" | "text/javascript" | "application/ecmascript"
|
||||
"application/javascript"
|
||||
| "text/javascript"
|
||||
| "application/ecmascript"
|
||||
| "text/ecmascript" => Some(Kind::Javascript),
|
||||
"text/css" => Some(Kind::Css),
|
||||
"text/html" | "application/xhtml+xml" => Some(Kind::Html),
|
||||
@@ -166,26 +168,19 @@ fn decode<'a>(body: &'a [u8], encoding: Option<&str>, limit: usize) -> Result<Co
|
||||
// Borrow them instead of creating a second full-size allocation.
|
||||
Ok(Cow::Borrowed(body))
|
||||
}
|
||||
Some("gzip") => read_bounded(flate2::read::GzDecoder::new(body), limit, "gzip")
|
||||
.map(Cow::Owned),
|
||||
Some("gzip") => {
|
||||
read_bounded(flate2::read::GzDecoder::new(body), limit, "gzip").map(Cow::Owned)
|
||||
}
|
||||
Some("deflate") => {
|
||||
let zlib = flate2::read::ZlibDecoder::new(body);
|
||||
match read_bounded(zlib, limit, "deflate") {
|
||||
Ok(value) => Ok(Cow::Owned(value)),
|
||||
Err(_) => read_bounded(
|
||||
flate2::read::DeflateDecoder::new(body),
|
||||
limit,
|
||||
"deflate",
|
||||
)
|
||||
.map(Cow::Owned),
|
||||
Err(_) => read_bounded(flate2::read::DeflateDecoder::new(body), limit, "deflate")
|
||||
.map(Cow::Owned),
|
||||
}
|
||||
}
|
||||
Some("br") => read_bounded(
|
||||
brotli::Decompressor::new(body, 16 * 1024),
|
||||
limit,
|
||||
"brotli",
|
||||
)
|
||||
.map(Cow::Owned),
|
||||
Some("br") => read_bounded(brotli::Decompressor::new(body, 16 * 1024), limit, "brotli")
|
||||
.map(Cow::Owned),
|
||||
Some("zstd") => {
|
||||
let decoder = zstd::stream::read::Decoder::new(body).context("open zstd decoder")?;
|
||||
read_bounded(decoder, limit, "zstd").map(Cow::Owned)
|
||||
@@ -204,7 +199,11 @@ fn drop_vary_accept_encoding(headers: &mut HeaderMap) {
|
||||
let Ok(value) = value.to_str() else {
|
||||
return;
|
||||
};
|
||||
for token in value.split(',').map(str::trim).filter(|token| !token.is_empty()) {
|
||||
for token in value
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|token| !token.is_empty())
|
||||
{
|
||||
if token == "*" {
|
||||
// `Vary: *` has special semantics and is rejected by the shared
|
||||
// cache. Do not rewrite it into a different policy.
|
||||
@@ -213,7 +212,10 @@ fn drop_vary_accept_encoding(headers: &mut HeaderMap) {
|
||||
if token.eq_ignore_ascii_case("accept-encoding") {
|
||||
continue;
|
||||
}
|
||||
if !retained.iter().any(|existing| existing.eq_ignore_ascii_case(token)) {
|
||||
if !retained
|
||||
.iter()
|
||||
.any(|existing| existing.eq_ignore_ascii_case(token))
|
||||
{
|
||||
retained.push(token.to_owned());
|
||||
}
|
||||
}
|
||||
@@ -235,8 +237,7 @@ fn transformed_etag(body: &[u8]) -> HeaderValue {
|
||||
let _ = write!(&mut value, "{byte:02x}");
|
||||
}
|
||||
value.push('"');
|
||||
HeaderValue::from_str(&value)
|
||||
.expect("sha256 validator is always a valid header")
|
||||
HeaderValue::from_str(&value).expect("sha256 validator is always a valid header")
|
||||
}
|
||||
|
||||
fn transform_memory_required_bytes(
|
||||
@@ -249,8 +250,8 @@ fn transform_memory_required_bytes(
|
||||
} else {
|
||||
content_length.checked_add(max_body_bytes.checked_mul(2)?)?
|
||||
};
|
||||
let units = estimated.checked_add(TRANSFORM_MEMORY_UNIT_BYTES - 1)?
|
||||
/ TRANSFORM_MEMORY_UNIT_BYTES;
|
||||
let units =
|
||||
estimated.checked_add(TRANSFORM_MEMORY_UNIT_BYTES - 1)? / TRANSFORM_MEMORY_UNIT_BYTES;
|
||||
units.max(1).checked_mul(TRANSFORM_MEMORY_UNIT_BYTES)
|
||||
}
|
||||
|
||||
@@ -279,12 +280,7 @@ fn try_reserve_memory_from(
|
||||
if next > budget_bytes {
|
||||
return None;
|
||||
}
|
||||
match counter.compare_exchange_weak(
|
||||
current,
|
||||
next,
|
||||
Ordering::AcqRel,
|
||||
Ordering::Acquire,
|
||||
) {
|
||||
match counter.compare_exchange_weak(current, next, Ordering::AcqRel, Ordering::Acquire) {
|
||||
Ok(_) => {
|
||||
return Some(TransformMemoryReservation {
|
||||
counter,
|
||||
@@ -300,11 +296,7 @@ fn try_reserve_transform_memory(
|
||||
required_bytes: usize,
|
||||
budget_bytes: usize,
|
||||
) -> Option<TransformMemoryReservation<'static>> {
|
||||
try_reserve_memory_from(
|
||||
&TRANSFORM_MEMORY_IN_USE_BYTES,
|
||||
required_bytes,
|
||||
budget_bytes,
|
||||
)
|
||||
try_reserve_memory_from(&TRANSFORM_MEMORY_IN_USE_BYTES, required_bytes, budget_bytes)
|
||||
}
|
||||
|
||||
struct TransformInflightReservation<'a> {
|
||||
@@ -327,12 +319,7 @@ fn try_reserve_slot_from(
|
||||
return None;
|
||||
}
|
||||
let next = current.checked_add(1)?;
|
||||
match counter.compare_exchange_weak(
|
||||
current,
|
||||
next,
|
||||
Ordering::AcqRel,
|
||||
Ordering::Acquire,
|
||||
) {
|
||||
match counter.compare_exchange_weak(current, next, Ordering::AcqRel, Ordering::Acquire) {
|
||||
Ok(_) => return Some(TransformInflightReservation { counter }),
|
||||
Err(actual) => current = actual,
|
||||
}
|
||||
|
||||
@@ -64,8 +64,12 @@ pub fn minify(body: &[u8]) -> Result<Vec<u8>> {
|
||||
}
|
||||
|
||||
fn needs_space(previous: Option<char>, next: Option<char>) -> bool {
|
||||
let Some(previous) = previous else { return false; };
|
||||
let Some(next) = next else { return false; };
|
||||
let Some(previous) = previous else {
|
||||
return false;
|
||||
};
|
||||
let Some(next) = next else {
|
||||
return false;
|
||||
};
|
||||
!matches!(previous, '{' | '}' | ':' | ';' | ',' | '>')
|
||||
&& !matches!(next, '{' | '}' | ':' | ';' | ',' | '>')
|
||||
}
|
||||
@@ -106,7 +106,11 @@ fn raw_tag(tag: &str) -> Option<(RawElement, bool, bool)> {
|
||||
return None;
|
||||
}
|
||||
let closing = inner.starts_with('/');
|
||||
let inner = if closing { inner[1..].trim_start() } else { inner };
|
||||
let inner = if closing {
|
||||
inner[1..].trim_start()
|
||||
} else {
|
||||
inner
|
||||
};
|
||||
let self_closing = inner.ends_with('/');
|
||||
let name = inner
|
||||
.split(|ch: char| ch.is_ascii_whitespace() || ch == '/' || ch == '>')
|
||||
|
||||
+12
-8
@@ -111,12 +111,10 @@ impl Default for Settings {
|
||||
html: false,
|
||||
svg: false,
|
||||
json: false,
|
||||
extensions: vec![
|
||||
"js", "mjs", "cjs", "css", "html", "htm", "svg", "json",
|
||||
]
|
||||
.into_iter()
|
||||
.map(str::to_owned)
|
||||
.collect(),
|
||||
extensions: vec!["js", "mjs", "cjs", "css", "html", "htm", "svg", "json"]
|
||||
.into_iter()
|
||||
.map(str::to_owned)
|
||||
.collect(),
|
||||
max_body_bytes: DEFAULT_MAX_BODY_BYTES,
|
||||
memory_budget_bytes: DEFAULT_MEMORY_BUDGET_BYTES,
|
||||
max_inflight: DEFAULT_MAX_INFLIGHT,
|
||||
@@ -137,7 +135,10 @@ impl Settings {
|
||||
(1..=1024).contains(&self.max_inflight),
|
||||
"transform.max_inflight must be 1..1024"
|
||||
);
|
||||
ensure!(self.extensions.len() <= 64, "at most 64 transform extensions");
|
||||
ensure!(
|
||||
self.extensions.len() <= 64,
|
||||
"at most 64 transform extensions"
|
||||
);
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for extension in &self.extensions {
|
||||
ensure!(
|
||||
@@ -293,7 +294,10 @@ impl Counters {
|
||||
.original_bytes
|
||||
.saturating_sub(applied.transformed_bytes) as u64,
|
||||
);
|
||||
*self.by_kind.entry(applied.kind.as_str().into()).or_default() += 1;
|
||||
*self
|
||||
.by_kind
|
||||
.entry(applied.kind.as_str().into())
|
||||
.or_default() += 1;
|
||||
}
|
||||
|
||||
fn record_skipped(&mut self, skipped: Skipped, elapsed: Duration) {
|
||||
|
||||
+37
-24
@@ -15,11 +15,7 @@ mod tests {
|
||||
Some(4 * 1024 * 1024)
|
||||
);
|
||||
assert_eq!(
|
||||
transform_memory_required_bytes(
|
||||
2 * 1024 * 1024,
|
||||
Some("gzip"),
|
||||
2 * 1024 * 1024
|
||||
),
|
||||
transform_memory_required_bytes(2 * 1024 * 1024, Some("gzip"), 2 * 1024 * 1024),
|
||||
Some(6 * 1024 * 1024)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -113,7 +109,9 @@ mod tests {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("content-length", HeaderValue::from_static("128"));
|
||||
assert!(apply(response, "/data.json", &settings, false).await.is_err());
|
||||
assert!(apply(response, "/data.json", &settings, false)
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -143,10 +141,7 @@ mod tests {
|
||||
assert_eq!(view.total.cache_hits, 1);
|
||||
assert_eq!(view.total.saved_bytes, 30);
|
||||
assert_eq!(view.total.by_kind.get("css"), Some(&1));
|
||||
assert_eq!(
|
||||
view.total.reasons.get("response_no_transform"),
|
||||
Some(&1)
|
||||
);
|
||||
assert_eq!(view.total.reasons.get("response_no_transform"), Some(&1));
|
||||
assert_eq!(view.scopes.len(), 1);
|
||||
assert_eq!(view.scopes[0].vhost, "app");
|
||||
}
|
||||
@@ -185,8 +180,13 @@ mod tests {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("vary", HeaderValue::from_static("Accept-Encoding"));
|
||||
let response = apply(response, "/data.json", &settings, false).await.unwrap();
|
||||
assert!(response.headers()["etag"].to_str().unwrap().starts_with("\"rustproxy-"));
|
||||
let response = apply(response, "/data.json", &settings, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(response.headers()["etag"]
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.starts_with("\"rustproxy-"));
|
||||
assert!(!response.headers().contains_key("vary"));
|
||||
assert!(response.extensions().get::<CanonicalIdentity>().is_some());
|
||||
let validators = response.extensions().get::<OriginValidators>().unwrap();
|
||||
@@ -213,11 +213,12 @@ mod tests {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("etag", HeaderValue::from_static("\"origin\""));
|
||||
response.headers_mut().insert(
|
||||
"vary",
|
||||
HeaderValue::from_static("Accept-Encoding, X-Theme"),
|
||||
);
|
||||
let response = apply(response, "/data.json", &settings, false).await.unwrap();
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("vary", HeaderValue::from_static("Accept-Encoding, X-Theme"));
|
||||
let response = apply(response, "/data.json", &settings, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.headers()["etag"], "\"origin\"");
|
||||
assert_eq!(response.headers()["vary"], "X-Theme");
|
||||
assert!(response.extensions().get::<CanonicalIdentity>().is_some());
|
||||
@@ -252,13 +253,18 @@ mod tests {
|
||||
.headers_mut()
|
||||
.insert("etag", HeaderValue::from_static("\"origin\""));
|
||||
|
||||
let response = apply(response, "/data.json", &settings, false).await.unwrap();
|
||||
let response = apply(response, "/data.json", &settings, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!response.headers().contains_key("content-encoding"));
|
||||
assert_eq!(
|
||||
response.headers()["content-length"].to_str().unwrap(),
|
||||
identity.len().to_string()
|
||||
);
|
||||
assert!(response.headers()["etag"].to_str().unwrap().starts_with("\"rustproxy-"));
|
||||
assert!(response.headers()["etag"]
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.starts_with("\"rustproxy-"));
|
||||
let validators = response.extensions().get::<OriginValidators>().unwrap();
|
||||
assert_eq!(validators.etag.as_ref().unwrap(), "\"origin\"");
|
||||
let applied = response.extensions().get::<Applied>().unwrap();
|
||||
@@ -287,7 +293,9 @@ mod tests {
|
||||
.headers_mut()
|
||||
.insert("etag", HeaderValue::from_static("\"origin\""));
|
||||
|
||||
let response = apply(response, "/data.json", &settings, true).await.unwrap();
|
||||
let response = apply(response, "/data.json", &settings, true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.headers()["etag"], "\"origin\"");
|
||||
assert!(response.extensions().get::<Applied>().is_none());
|
||||
let received = response.into_body().collect().await.unwrap().to_bytes();
|
||||
@@ -315,7 +323,9 @@ mod tests {
|
||||
HeaderValue::from_static("public, max-age=60, no-transform"),
|
||||
);
|
||||
|
||||
let response = apply(response, "/data.json", &settings, false).await.unwrap();
|
||||
let response = apply(response, "/data.json", &settings, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(response.extensions().get::<Applied>().is_none());
|
||||
let received = response.into_body().collect().await.unwrap().to_bytes();
|
||||
assert_eq!(&received[..], body);
|
||||
@@ -339,7 +349,9 @@ mod tests {
|
||||
.headers_mut()
|
||||
.insert("etag", HeaderValue::from_static("\"origin-head\""));
|
||||
|
||||
let response = apply(response, "/data.json", &settings, false).await.unwrap();
|
||||
let response = apply(response, "/data.json", &settings, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.headers()["content-length"], "76");
|
||||
assert_eq!(response.headers()["etag"], "\"origin-head\"");
|
||||
assert!(response.extensions().get::<Applied>().is_none());
|
||||
@@ -383,7 +395,9 @@ mod tests {
|
||||
.headers_mut()
|
||||
.insert("etag", HeaderValue::from_static("\"origin\""));
|
||||
|
||||
let response = apply(response, "/data.json", &settings, false).await.unwrap();
|
||||
let response = apply(response, "/data.json", &settings, false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.headers()["content-encoding"], "gzip");
|
||||
assert_eq!(response.headers()["etag"], "\"origin\"");
|
||||
assert_eq!(response.headers()["vary"], "Accept-Encoding");
|
||||
@@ -391,5 +405,4 @@ mod tests {
|
||||
let received = response.into_body().collect().await.unwrap().to_bytes();
|
||||
assert_eq!(&received[..], encoded.as_slice());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user