v0.34.0_2
This commit is contained in:
1 parent
434e789144
commit
9cfc4588cc
18 files changed
+392
-121
No files matched your search
@@ -4193,7 +4193,7 @@
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 64,
|
||||
"default": 4
|
||||
"default": 64
|
||||
},
|
||||
"tcp_keepalive_seconds": {
|
||||
"type": "integer",
|
||||
|
||||
@@ -195,7 +195,7 @@ function load(value = defaults(), migrationIds = [], show = true) {
|
||||
note(g, "vhRetryHelp");
|
||||
field(g, "keepalive", "vhKeepalive", keepalive.enabled !== false, "checkbox");
|
||||
field(g, "idle-timeout", "vhIdleTimeout", keepalive.idle_timeout_seconds ?? 30, "number", { required: true, min: 1, max: 3600 });
|
||||
field(g, "max-idle", "vhMaxIdle", keepalive.max_idle_per_host ?? 4, "number", { required: true, min: 1, max: 64 });
|
||||
field(g, "max-idle", "vhMaxIdle", keepalive.max_idle_per_host ?? 64, "number", { required: true, min: 1, max: 64 });
|
||||
field(g, "tcp-keepalive", "vhTcpKeepalive", keepalive.tcp_keepalive_seconds ?? 30, "number", { required: true, min: 1, max: 3600 });
|
||||
field(g, "pool-recycle-requests", "vhPoolRecycleRequests", keepalive.pool_recycle_requests ?? 25000, "number", { required: true, min: 0, max: 10000000 });
|
||||
field(g, "pool-recycle-seconds", "vhPoolRecycleSeconds", keepalive.pool_recycle_seconds ?? 3600, "number", { required: true, min: 0, max: 604800 });
|
||||
|
||||
@@ -70,7 +70,10 @@ impl Default for Keepalive {
|
||||
Self {
|
||||
enabled: true,
|
||||
idle_timeout_seconds: 30,
|
||||
max_idle_per_host: 4,
|
||||
// Retain enough reusable H1 upstream sockets under concurrent
|
||||
// small-response traffic. The existing 1..=64 bound, idle
|
||||
// timeout and pool recycling still limit resource retention.
|
||||
max_idle_per_host: 64,
|
||||
tcp_keepalive_seconds: 30,
|
||||
pool_recycle_requests: 25_000,
|
||||
pool_recycle_seconds: 3_600,
|
||||
|
||||
@@ -102,6 +102,11 @@ mod tests {
|
||||
assert!(parsed.retry.retry_connect_errors);
|
||||
assert!(parsed.retry.retry_timeouts);
|
||||
assert_eq!(parsed.retry.retry_statuses, vec![502, 503, 504]);
|
||||
assert_eq!(parsed.keepalive.max_idle_per_host, 64);
|
||||
let explicit: Settings = serde_json::from_value(serde_json::json!({
|
||||
"keepalive": { "max_idle_per_host": 4 }
|
||||
})).unwrap();
|
||||
assert_eq!(explicit.keepalive.max_idle_per_host, 4);
|
||||
assert_eq!(parsed.keepalive.pool_recycle_requests, 25_000);
|
||||
assert_eq!(parsed.keepalive.pool_recycle_seconds, 3_600);
|
||||
assert_eq!(parsed.keepalive.http1_max_buffer_bytes, 131_072);
|
||||
|
||||
+14
-15
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user