v0.34.0_2

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-18 06:13:42 +02:00
1 parent 434e789144
commit 9cfc4588cc
18 files changed
+392 -121

No files matched your search

+14 -15
View File
@@ -176,30 +176,28 @@ impl Drop for Exchange {
_ => &self.metrics.status_5xx,
}
.fetch_add(1, Ordering::Relaxed);
// Final Arc drop has exclusive access: move strings without locking.
let backend = self
.backend
.lock()
.get_mut()
.unwrap_or_else(|e| e.into_inner())
.as_ref()
.map(|l| l.member.backend.id.clone());
let websocket_phase = *self
.websocket_phase
.lock()
.unwrap_or_else(|e| e.into_inner());
let route = self.route.lock().unwrap_or_else(|e| e.into_inner()).clone();
let websocket_phase = *self.websocket_phase.get_mut().unwrap_or_else(|e| e.into_inner());
let route = self.route.get_mut().unwrap_or_else(|e| e.into_inner()).take();
let source = ExchangeSource::name(self.source.load(Ordering::Relaxed));
let cache_effective_enabled = read_effective(self.cache_effective_enabled.load(Ordering::Relaxed));
let transform_effective_enabled = read_effective(self.transform_effective_enabled.load(Ordering::Relaxed));
let client_ip = self
.client_ip
.lock()
.get_mut()
.unwrap_or_else(|e| e.into_inner())
.clone();
.take();
let country = self
.country
.lock()
.get_mut()
.unwrap_or_else(|e| e.into_inner())
.clone();
.take();
let request_bytes = self.request_bytes.load(Ordering::Relaxed);
let response_bytes = self.response_bytes.load(Ordering::Relaxed);
let duration_ms = duration.as_millis() as u64;
@@ -248,7 +246,7 @@ impl Drop for Exchange {
sni: None,
authority: None,
matched_by: None,
cache_reason: *self.cache_reason.lock().unwrap_or_else(|e| e.into_inner()),
cache_reason: *self.cache_reason.get_mut().unwrap_or_else(|e| e.into_inner()),
vhost: self.vhost.take(),
route,
cache_effective_enabled,
@@ -266,18 +264,18 @@ impl Drop for Exchange {
websocket_phase,
websocket_error: *self
.websocket_error
.lock()
.get_mut()
.unwrap_or_else(|e| e.into_inner()),
h3_reset_code: *self.h3_reset_code.lock().unwrap_or_else(|e| e.into_inner()),
h3_reset_code: *self.h3_reset_code.get_mut().unwrap_or_else(|e| e.into_inner()),
upstream_status: match self.upstream_status.load(Ordering::Relaxed) {
0 => None,
value => Some(value),
},
upstream_error: self
.upstream_error
.lock()
.get_mut()
.unwrap_or_else(|e| e.into_inner())
.clone(),
.take(),
client_ip,
user_agent: self.user_agent.take(),
country,
@@ -290,6 +288,7 @@ impl Drop for Exchange {
});
}
}
#[derive(Clone, Copy)]
pub enum Direction {
Request,
+24
View File
@@ -226,6 +226,30 @@ mod tests {
}
}
#[tokio::test]
async fn completion_moves_owned_fields_but_preserves_log_identity() {
let (exchange, slots) = observation().await;
let logs = exchange.logs.clone();
*exchange.route.lock().unwrap() = Some("route-1".into());
*exchange.client_ip.lock().unwrap() = Some("198.51.100.9".into());
*exchange.country.lock().unwrap() = Some("DE".into());
*exchange.upstream_error.lock().unwrap() = Some("upstream closed".into());
*exchange.cache_reason.lock().unwrap() = "cache_disabled";
*exchange.websocket_error.lock().unwrap() = Some("upstream_http_error");
*exchange.h3_reset_code.lock().unwrap() = Some("h3_general_protocol_error");
drop(exchange);
assert_eq!(slots.available_permits(), 1);
let entries = logs.recent();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].route.as_deref(), Some("route-1"));
assert_eq!(entries[0].client_ip.as_deref(), Some("198.51.100.9"));
assert_eq!(entries[0].country.as_deref(), Some("DE"));
assert_eq!(entries[0].upstream_error.as_deref(), Some("upstream closed"));
assert_eq!(entries[0].cache_reason, "cache_disabled");
assert_eq!(entries[0].websocket_error, Some("upstream_http_error"));
assert_eq!(entries[0].h3_reset_code, Some("h3_general_protocol_error"));
}
#[tokio::test]
async fn atomic_exchange_tags_keep_original_observability_fields() {
let (exchange, slots) = observation().await;
+6
View File
@@ -45,6 +45,9 @@ include!("headers/tests.rs");
/// guarded self-admin reverse-proxy path may restore only those fragments after
/// the actual backend has been selected and verified.
pub fn strip_admin_cookie(headers: &mut HeaderMap) -> Vec<String> {
if !headers.contains_key(http::header::COOKIE) {
return Vec::new();
}
let mut retained = Vec::new();
let mut admin = Vec::new();
for part in headers
@@ -91,6 +94,9 @@ pub fn restore_admin_cookie(headers: &mut HeaderMap, admin: &[String]) {
}
/// Upstreams cannot overwrite the reserved admin cookie through this proxy listener.
pub fn strip_admin_set_cookie(headers: &mut HeaderMap) {
if !headers.contains_key(http::header::SET_COOKIE) {
return;
}
let retained: Vec<_> = headers
.get_all(http::header::SET_COOKIE)
.iter()
+23
View File
@@ -19,6 +19,29 @@ mod tests {
h.insert("connection", "keep-alive, Upgrade".parse().unwrap());
assert!(requests_upgrade(&h));
}
#[test]
fn absent_cookies_leave_headers_unchanged() {
let mut headers = HeaderMap::new();
headers.insert("x-pass", "yes".parse().unwrap());
assert!(strip_admin_cookie(&mut headers).is_empty());
strip_admin_set_cookie(&mut headers);
assert_eq!(headers.get("x-pass").unwrap(), "yes");
assert!(!headers.contains_key(http::header::COOKIE));
assert!(!headers.contains_key(http::header::SET_COOKIE));
}
#[test]
fn set_cookie_keeps_valid_unreserved_values() {
let mut headers = HeaderMap::new();
headers.append("set-cookie", "a=1; Secure".parse().unwrap());
headers.append("set-cookie", "rustproxy_session=secret".parse().unwrap());
headers.append("set-cookie", "b=2; HttpOnly".parse().unwrap());
strip_admin_set_cookie(&mut headers);
let cookies: Vec<_> = headers.get_all("set-cookie").iter()
.map(|value| value.to_str().unwrap()).collect();
assert_eq!(cookies, vec!["a=1; Secure", "b=2; HttpOnly"]);
}
#[test]
fn admin_cookie_can_be_restored_only_from_captured_fragment() {
let mut h = HeaderMap::new();