From 52858d39f43788b3bd547875490ae9aff1ca5a6b Mon Sep 17 00:00:00 2001 From: RoboShyim Date: Sat, 31 Jan 2026 09:04:24 +0000 Subject: [PATCH 1/2] Derive sw-cache-hash header from cookie --- src/main.rs | 5 +++++ src/normalize.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 72deaf1..b9efd00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,6 +48,11 @@ async fn handle( ConnectInfo(peer): ConnectInfo, req: Request, ) -> impl IntoResponse { + // Request normalization (Shopware parity) + // (e.g. derive sw-cache-hash header from cookie when not explicitly set) + let mut req = req; + normalize::apply_shopware_request_normalization(req.headers_mut()); + let method = req.method().clone(); let uri = req.uri().clone(); diff --git a/src/normalize.rs b/src/normalize.rs index 5dff941..fcd4936 100644 --- a/src/normalize.rs +++ b/src/normalize.rs @@ -25,6 +25,40 @@ pub fn apply_forwarded_for(headers: &mut HeaderMap, ip: std::net::IpAddr) { headers.insert("x-forwarded-for", HeaderValue::from_str(&new_val).unwrap()); } +/// Shopware VCL parity: set `sw-cache-hash` header from cookie when missing. +/// +/// In Varnish, `req.http.sw-cache-hash` is derived from the cookie in vcl_recv. +pub fn apply_shopware_request_normalization(headers: &mut HeaderMap) { + // Header wins. + if headers + .get("sw-cache-hash") + .and_then(|v| v.to_str().ok()) + .is_some_and(|v| !v.is_empty()) + { + return; + } + + let cookie = headers.get(http::header::COOKIE).and_then(|v| v.to_str().ok()).unwrap_or(""); + if cookie.is_empty() { + return; + } + + for part in cookie.split(';') { + let p = part.trim(); + if let Some((k, v)) = p.split_once('=') { + if k.trim() == "sw-cache-hash" { + let v = v.trim(); + if !v.is_empty() { + if let Ok(hv) = HeaderValue::from_str(v) { + headers.insert("sw-cache-hash", hv); + } + } + return; + } + } + } +} + pub fn build_upstream_url(origin: &str, uri: &Uri) -> String { // origin like http://host:port let mut base = origin.trim_end_matches('/').to_string(); @@ -51,9 +85,6 @@ pub fn should_short_circuit_widgets_checkout_info(req: &http::Requestheader extraction into request normalization.) return true; } @@ -202,12 +233,33 @@ mod tests { } #[test] +<<<<<<< HEAD fn widgets_checkout_info_short_circuit_rules() { // missing header => short circuit let req = http::Request::builder() .uri("/widgets/checkout/info") .body(axum::body::Body::empty()) .unwrap(); +======= + fn request_normalization_sets_sw_cache_hash_from_cookie() { + let mut headers = HeaderMap::new(); + headers.insert(http::header::COOKIE, HeaderValue::from_static("a=1; sw-cache-hash=xyz; b=2")); + apply_shopware_request_normalization(&mut headers); + assert_eq!(headers.get("sw-cache-hash").unwrap().to_str().unwrap(), "xyz"); + + // header wins + let mut headers = HeaderMap::new(); + headers.insert("sw-cache-hash", HeaderValue::from_static("hdr")); + headers.insert(http::header::COOKIE, HeaderValue::from_static("sw-cache-hash=cookie")); + apply_shopware_request_normalization(&mut headers); + assert_eq!(headers.get("sw-cache-hash").unwrap().to_str().unwrap(), "hdr"); + } + + #[test] + fn widgets_checkout_info_short_circuit_rules() { + // missing header => short circuit + let req = http::Request::builder().uri("/widgets/checkout/info").body(axum::body::Body::empty()).unwrap(); +>>>>>>> b361b93 (Derive sw-cache-hash header from cookie) assert!(should_short_circuit_widgets_checkout_info(&req)); // header present and no sw-states cookie => do not short circuit From 861c7d5213507b50d0b77bbeed6053a2e915025a Mon Sep 17 00:00:00 2001 From: RoboShyim Date: Sat, 31 Jan 2026 10:14:10 +0000 Subject: [PATCH 2/2] Resolve rebase conflicts --- src/normalize.rs | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/normalize.rs b/src/normalize.rs index fcd4936..891c6d4 100644 --- a/src/normalize.rs +++ b/src/normalize.rs @@ -38,7 +38,10 @@ pub fn apply_shopware_request_normalization(headers: &mut HeaderMap) { return; } - let cookie = headers.get(http::header::COOKIE).and_then(|v| v.to_str().ok()).unwrap_or(""); + let cookie = headers + .get(http::header::COOKIE) + .and_then(|v| v.to_str().ok()) + .unwrap_or(""); if cookie.is_empty() { return; } @@ -233,33 +236,39 @@ mod tests { } #[test] -<<<<<<< HEAD - fn widgets_checkout_info_short_circuit_rules() { - // missing header => short circuit - let req = http::Request::builder() - .uri("/widgets/checkout/info") - .body(axum::body::Body::empty()) - .unwrap(); -======= fn request_normalization_sets_sw_cache_hash_from_cookie() { let mut headers = HeaderMap::new(); - headers.insert(http::header::COOKIE, HeaderValue::from_static("a=1; sw-cache-hash=xyz; b=2")); + headers.insert( + http::header::COOKIE, + HeaderValue::from_static("a=1; sw-cache-hash=xyz; b=2"), + ); apply_shopware_request_normalization(&mut headers); - assert_eq!(headers.get("sw-cache-hash").unwrap().to_str().unwrap(), "xyz"); + assert_eq!( + headers.get("sw-cache-hash").unwrap().to_str().unwrap(), + "xyz" + ); // header wins let mut headers = HeaderMap::new(); headers.insert("sw-cache-hash", HeaderValue::from_static("hdr")); - headers.insert(http::header::COOKIE, HeaderValue::from_static("sw-cache-hash=cookie")); + headers.insert( + http::header::COOKIE, + HeaderValue::from_static("sw-cache-hash=cookie"), + ); apply_shopware_request_normalization(&mut headers); - assert_eq!(headers.get("sw-cache-hash").unwrap().to_str().unwrap(), "hdr"); + assert_eq!( + headers.get("sw-cache-hash").unwrap().to_str().unwrap(), + "hdr" + ); } #[test] fn widgets_checkout_info_short_circuit_rules() { // missing header => short circuit - let req = http::Request::builder().uri("/widgets/checkout/info").body(axum::body::Body::empty()).unwrap(); ->>>>>>> b361b93 (Derive sw-cache-hash header from cookie) + let req = http::Request::builder() + .uri("/widgets/checkout/info") + .body(axum::body::Body::empty()) + .unwrap(); assert!(should_short_circuit_widgets_checkout_info(&req)); // header present and no sw-states cookie => do not short circuit