diff --git a/.bleep b/.bleep index fcd670e9..9112e673 100644 --- a/.bleep +++ b/.bleep @@ -1 +1 @@ -bed451a69900aff81befc209090ce68cc61f8c5a +f4e5ae2d44c6e580a5a9a7cc5a80b07c69c95840 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8760d5b8..d3c8603b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ http = "1" log = "0.4" h2 = ">=0.4.11" once_cell = "1" -lru = "0.14" +lru = "0.16.3" ahash = ">=0.8.9" [profile.bench] diff --git a/pingora-boringssl/Cargo.toml b/pingora-boringssl/Cargo.toml index ec0b7bc0..43820f5e 100644 --- a/pingora-boringssl/Cargo.toml +++ b/pingora-boringssl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-boringssl" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" diff --git a/pingora-boringssl/src/boring_tokio.rs b/pingora-boringssl/src/boring_tokio.rs index deb4842c..ef5d60c2 100644 --- a/pingora-boringssl/src/boring_tokio.rs +++ b/pingora-boringssl/src/boring_tokio.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-boringssl/src/ext.rs b/pingora-boringssl/src/ext.rs index 0af2bb0b..256e4ac5 100644 --- a/pingora-boringssl/src/ext.rs +++ b/pingora-boringssl/src/ext.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-boringssl/src/lib.rs b/pingora-boringssl/src/lib.rs index dd560a84..9701c598 100644 --- a/pingora-boringssl/src/lib.rs +++ b/pingora-boringssl/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/Cargo.toml b/pingora-cache/Cargo.toml index 7b809dcc..4e54ca64 100644 --- a/pingora-cache/Cargo.toml +++ b/pingora-cache/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-cache" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -18,12 +18,12 @@ name = "pingora_cache" path = "src/lib.rs" [dependencies] -pingora-core = { version = "0.6.0", path = "../pingora-core", default-features = false } -pingora-error = { version = "0.6.0", path = "../pingora-error" } -pingora-header-serde = { version = "0.6.0", path = "../pingora-header-serde" } -pingora-http = { version = "0.6.0", path = "../pingora-http" } -pingora-lru = { version = "0.6.0", path = "../pingora-lru" } -pingora-timeout = { version = "0.6.0", path = "../pingora-timeout" } +pingora-core = { version = "0.7.0", path = "../pingora-core", default-features = false } +pingora-error = { version = "0.7.0", path = "../pingora-error" } +pingora-header-serde = { version = "0.7.0", path = "../pingora-header-serde" } +pingora-http = { version = "0.7.0", path = "../pingora-http" } +pingora-lru = { version = "0.7.0", path = "../pingora-lru" } +pingora-timeout = { version = "0.7.0", path = "../pingora-timeout" } bstr = { workspace = true } http = { workspace = true } indexmap = "1" @@ -31,7 +31,7 @@ once_cell = { workspace = true } regex = "1" blake2 = "0.10" serde = { version = "1.0", features = ["derive"] } -rmp-serde = "1" +rmp-serde = "1.3.0" bytes = { workspace = true } httpdate = "1.0.2" log = { workspace = true } @@ -39,7 +39,7 @@ async-trait = { workspace = true } parking_lot = "0.12" cf-rustracing = "1.0" cf-rustracing-jaeger = "1.0" -rmp = "0.8" +rmp = "0.8.14" tokio = { workspace = true } lru = { workspace = true } ahash = { workspace = true } diff --git a/pingora-cache/benches/lru_memory.rs b/pingora-cache/benches/lru_memory.rs index 1d0678dc..67428671 100644 --- a/pingora-cache/benches/lru_memory.rs +++ b/pingora-cache/benches/lru_memory.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/benches/lru_serde.rs b/pingora-cache/benches/lru_serde.rs index 5c0809e4..237a827e 100644 --- a/pingora-cache/benches/lru_serde.rs +++ b/pingora-cache/benches/lru_serde.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/benches/simple_lru_memory.rs b/pingora-cache/benches/simple_lru_memory.rs index 30500c72..fa1199e3 100644 --- a/pingora-cache/benches/simple_lru_memory.rs +++ b/pingora-cache/benches/simple_lru_memory.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/cache_control.rs b/pingora-cache/src/cache_control.rs index f4203205..98af7fbb 100644 --- a/pingora-cache/src/cache_control.rs +++ b/pingora-cache/src/cache_control.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/eviction/lru.rs b/pingora-cache/src/eviction/lru.rs index 11b7fb02..d241ee69 100644 --- a/pingora-cache/src/eviction/lru.rs +++ b/pingora-cache/src/eviction/lru.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/eviction/mod.rs b/pingora-cache/src/eviction/mod.rs index eb757bb0..0e78fbe1 100644 --- a/pingora-cache/src/eviction/mod.rs +++ b/pingora-cache/src/eviction/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/eviction/simple_lru.rs b/pingora-cache/src/eviction/simple_lru.rs index 039ada53..1c887552 100644 --- a/pingora-cache/src/eviction/simple_lru.rs +++ b/pingora-cache/src/eviction/simple_lru.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/filters.rs b/pingora-cache/src/filters.rs index 5ad74916..607e6303 100644 --- a/pingora-cache/src/filters.rs +++ b/pingora-cache/src/filters.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/hashtable.rs b/pingora-cache/src/hashtable.rs index fd5008d4..07ca5f3f 100644 --- a/pingora-cache/src/hashtable.rs +++ b/pingora-cache/src/hashtable.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/key.rs b/pingora-cache/src/key.rs index 0e2d51a6..c9a599a9 100644 --- a/pingora-cache/src/key.rs +++ b/pingora-cache/src/key.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/lib.rs b/pingora-cache/src/lib.rs index 5dd89505..867cff08 100644 --- a/pingora-cache/src/lib.rs +++ b/pingora-cache/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -398,8 +398,7 @@ impl HttpCache { OriginNotCache | ResponseTooLarge | PredictedResponseTooLarge => { LockStatus::GiveUp } - // not sure which LockStatus make sense, we treat it as GiveUp for now - Custom(_) => LockStatus::GiveUp, + Custom(reason) => lock_ctx.cache_lock.custom_lock_status(reason), // should never happen, NeverEnabled shouldn't hold a lock NeverEnabled => panic!("NeverEnabled holds a write lock"), CacheLockGiveUp | CacheLockTimeout => { diff --git a/pingora-cache/src/lock.rs b/pingora-cache/src/lock.rs index e58e2f2d..5633b09c 100644 --- a/pingora-cache/src/lock.rs +++ b/pingora-cache/src/lock.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -45,6 +45,13 @@ pub trait CacheKeyLock { let tag_value: &'static str = lock_status.into(); span.set_tag(|| Tag::new("status", tag_value)); } + + /// Set a lock status for a custom `NoCacheReason`. + fn custom_lock_status(&self, _custom_no_cache: &'static str) -> LockStatus { + // treat custom no cache reasons as GiveUp by default + // (like OriginNotCache) + LockStatus::GiveUp + } } const N_SHARDS: usize = 16; diff --git a/pingora-cache/src/max_file_size.rs b/pingora-cache/src/max_file_size.rs index 106b012e..7c9eccd9 100644 --- a/pingora-cache/src/max_file_size.rs +++ b/pingora-cache/src/max_file_size.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/memory.rs b/pingora-cache/src/memory.rs index 786cf453..6ab57c80 100644 --- a/pingora-cache/src/memory.rs +++ b/pingora-cache/src/memory.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/meta.rs b/pingora-cache/src/meta.rs index 40fc20cc..4545ee22 100644 --- a/pingora-cache/src/meta.rs +++ b/pingora-cache/src/meta.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -93,8 +93,10 @@ mod internal_meta { // schema to decode it // After full releases, remove `skip_serializing_if` so that we can add the next extended field. #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub(crate) variance: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) epoch_override: Option, } impl Default for InternalMetaV2 { @@ -108,6 +110,7 @@ mod internal_meta { stale_while_revalidate_sec: 0, stale_if_error_sec: 0, variance: None, + epoch_override: None, } } } @@ -258,35 +261,75 @@ mod internal_meta { assert_eq!(meta2.created, meta2.updated); } - #[test] - fn test_internal_meta_serde_v2_extend_fields() { - // make sure that v2 format is backward compatible - // this is the base version of v2 without any extended fields - #[derive(Deserialize, Serialize)] - pub(crate) struct InternalMetaV2Base { - pub(crate) version: u8, - pub(crate) fresh_until: SystemTime, - pub(crate) created: SystemTime, - pub(crate) updated: SystemTime, - pub(crate) stale_while_revalidate_sec: u32, - pub(crate) stale_if_error_sec: u32, + // make sure that v2 format is backward compatible + // this is the base version of v2 without any extended fields + #[derive(Deserialize, Serialize)] + struct InternalMetaV2Base { + version: u8, + fresh_until: SystemTime, + created: SystemTime, + updated: SystemTime, + stale_while_revalidate_sec: u32, + stale_if_error_sec: u32, + } + + impl InternalMetaV2Base { + pub const VERSION: u8 = 2; + pub fn serialize(&self) -> Result> { + assert!(self.version >= Self::VERSION); + rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta") + } + fn deserialize(buf: &[u8]) -> Result { + rmp_serde::decode::from_slice(buf) + .or_err(InternalError, "failed to decode cache meta v2") } + } - impl InternalMetaV2Base { - pub const VERSION: u8 = 2; - pub fn serialize(&self) -> Result> { - assert!(self.version >= Self::VERSION); - rmp_serde::encode::to_vec(self) - .or_err(InternalError, "failed to encode cache meta") - } - fn deserialize(buf: &[u8]) -> Result { - rmp_serde::decode::from_slice(buf) - .or_err(InternalError, "failed to decode cache meta v2") + // this is the base version of v2 with variance but without epoch_override + #[derive(Deserialize, Serialize)] + struct InternalMetaV2BaseWithVariance { + version: u8, + fresh_until: SystemTime, + created: SystemTime, + updated: SystemTime, + stale_while_revalidate_sec: u32, + stale_if_error_sec: u32, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + variance: Option, + } + + impl Default for InternalMetaV2BaseWithVariance { + fn default() -> Self { + let epoch = SystemTime::UNIX_EPOCH; + InternalMetaV2BaseWithVariance { + version: InternalMetaV2::VERSION, + fresh_until: epoch, + created: epoch, + updated: epoch, + stale_while_revalidate_sec: 0, + stale_if_error_sec: 0, + variance: None, } } + } + impl InternalMetaV2BaseWithVariance { + pub const VERSION: u8 = 2; + pub fn serialize(&self) -> Result> { + assert!(self.version >= Self::VERSION); + rmp_serde::encode::to_vec(self).or_err(InternalError, "failed to encode cache meta") + } + fn deserialize(buf: &[u8]) -> Result { + rmp_serde::decode::from_slice(buf) + .or_err(InternalError, "failed to decode cache meta v2") + } + } + + #[test] + fn test_internal_meta_serde_v2_extend_fields_variance() { // ext V2 to base v2 - let meta = InternalMetaV2::default(); + let meta = InternalMetaV2BaseWithVariance::default(); let binary = meta.serialize().unwrap(); let meta2 = InternalMetaV2Base::deserialize(&binary).unwrap(); assert_eq!(meta2.version, 2); @@ -305,11 +348,62 @@ mod internal_meta { stale_if_error_sec: 0, }; let binary = meta.serialize().unwrap(); + let meta2 = InternalMetaV2BaseWithVariance::deserialize(&binary).unwrap(); + assert_eq!(meta2.version, 2); + assert_eq!(meta.fresh_until, meta2.fresh_until); + assert_eq!(meta.created, meta2.created); + assert_eq!(meta.updated, meta2.updated); + } + + #[test] + fn test_internal_meta_serde_v2_extend_fields_epoch_override() { + let now = SystemTime::now(); + + // ext V2 (with epoch_override = None) to V2 with variance (without epoch_override field) + let meta = InternalMetaV2 { + fresh_until: now, + created: now, + updated: now, + epoch_override: None, // None means it will be skipped during serialization + ..Default::default() + }; + let binary = meta.serialize().unwrap(); + let meta2 = InternalMetaV2BaseWithVariance::deserialize(&binary).unwrap(); + assert_eq!(meta2.version, 2); + assert_eq!(meta.fresh_until, meta2.fresh_until); + assert_eq!(meta.created, meta2.created); + assert_eq!(meta.updated, meta2.updated); + assert!(meta2.variance.is_none()); + + // V2 base with variance (without epoch_override) to ext V2 (with epoch_override) + let mut meta = InternalMetaV2BaseWithVariance { + version: InternalMetaV2::VERSION, + fresh_until: now, + created: now, + updated: now, + stale_while_revalidate_sec: 0, + stale_if_error_sec: 0, + variance: None, + }; + let binary = meta.serialize().unwrap(); let meta2 = InternalMetaV2::deserialize(&binary).unwrap(); assert_eq!(meta2.version, 2); assert_eq!(meta.fresh_until, meta2.fresh_until); assert_eq!(meta.created, meta2.created); assert_eq!(meta.updated, meta2.updated); + assert!(meta2.variance.is_none()); + assert!(meta2.epoch_override.is_none()); + + // try with variance set + meta.variance = Some(*b"variance_testing"); + let binary = meta.serialize().unwrap(); + let meta2 = InternalMetaV2::deserialize(&binary).unwrap(); + assert_eq!(meta2.version, 2); + assert_eq!(meta.fresh_until, meta2.fresh_until); + assert_eq!(meta.created, meta2.created); + assert_eq!(meta.updated, meta2.updated); + assert_eq!(meta.variance, meta2.variance); + assert!(meta2.epoch_override.is_none()); } } } @@ -364,6 +458,32 @@ impl CacheMeta { self.0.internal.updated } + /// The reference point for cache age. This represents the "starting point" for `fresh_until`. + /// + /// This defaults to the `updated` timestamp but is overridden by the `epoch_override` field + /// if set. + pub fn epoch(&self) -> SystemTime { + self.0.internal.epoch_override.unwrap_or(self.updated()) + } + + /// Get the epoch override for this asset + pub fn epoch_override(&self) -> Option { + self.0.internal.epoch_override + } + + /// Set the epoch override for this asset + /// + /// When set, this will be used as the reference point for calculating age and freshness + /// instead of the updated time. + pub fn set_epoch_override(&mut self, epoch: SystemTime) { + self.0.internal.epoch_override = Some(epoch); + } + + /// Remove the epoch override for this asset + pub fn remove_epoch_override(&mut self) { + self.0.internal.epoch_override = None; + } + /// Is the asset still valid pub fn is_fresh(&self, time: SystemTime) -> bool { // NOTE: HTTP cache time resolution is second @@ -372,15 +492,17 @@ impl CacheMeta { /// How long (in seconds) the asset should be fresh since its admission/revalidation /// - /// This is essentially the max-age value (or its equivalence) + /// This is essentially the max-age value (or its equivalence). + /// If an epoch override is set, it will be used as the reference point instead of the updated time. pub fn fresh_sec(&self) -> u64 { // swallow `duration_since` error, assets that are always stale have earlier `fresh_until` than `created` // practically speaking we can always treat these as 0 ttl // XXX: return Error if `fresh_until` is much earlier than expected? + let reference = self.epoch(); self.0 .internal .fresh_until - .duration_since(self.0.internal.updated) + .duration_since(reference) .map_or(0, |duration| duration.as_secs()) } @@ -390,9 +512,12 @@ impl CacheMeta { } /// How old the asset is since its admission/revalidation + /// + /// If an epoch override is set, it will be used as the reference point instead of the updated time. pub fn age(&self) -> Duration { + let reference = self.epoch(); SystemTime::now() - .duration_since(self.updated()) + .duration_since(reference) .unwrap_or_default() } @@ -617,3 +742,93 @@ pub fn set_compression_dict_path(path: &str) -> bool { pub fn set_compression_dict_content(content: Cow<'static, [u8]>) -> bool { COMPRESSION_DICT_CONTENT.set(content).is_ok() } + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + #[test] + fn test_cache_meta_age_without_override() { + let now = SystemTime::now(); + let header = ResponseHeader::build_no_case(200, None).unwrap(); + let meta = CacheMeta::new(now + Duration::from_secs(300), now, 0, 0, header); + + // Without epoch_override, age() should use updated() as reference + std::thread::sleep(Duration::from_millis(100)); + let age = meta.age(); + assert!(age.as_secs() < 1, "age should be close to 0"); + + // epoch() should return updated() when no override is set + assert_eq!(meta.epoch(), meta.updated()); + } + + #[test] + fn test_cache_meta_age_with_epoch_override_past() { + let now = SystemTime::now(); + let header = ResponseHeader::build(200, None).unwrap(); + let mut meta = CacheMeta::new(now + Duration::from_secs(300), now, 0, 0, header); + + // Set epoch_override to 10 seconds in the past + let epoch_override = now - Duration::from_secs(10); + meta.set_epoch_override(epoch_override); + + // age() should now use epoch_override as the reference + let age = meta.age(); + assert!(age.as_secs() >= 10); + assert!(age.as_secs() < 12); + + // epoch() should return the override + assert_eq!(meta.epoch(), epoch_override); + assert_eq!(meta.epoch_override(), Some(epoch_override)); + } + + #[test] + fn test_cache_meta_age_with_epoch_override_future() { + let now = SystemTime::now(); + let header = ResponseHeader::build(200, None).unwrap(); + let mut meta = CacheMeta::new(now + Duration::from_secs(100), now, 0, 0, header); + + // Set epoch_override to a future time + let future_epoch = now + Duration::from_secs(10); + meta.set_epoch_override(future_epoch); + + let age_with_epoch = meta.age(); + // age should be 0 since epoch_override is in the future + assert_eq!(age_with_epoch, Duration::ZERO); + } + + #[test] + fn test_cache_meta_fresh_sec() { + let header = ResponseHeader::build(StatusCode::OK, None).unwrap(); + let mut meta = CacheMeta::new( + SystemTime::now() + Duration::from_secs(100), + SystemTime::now() - Duration::from_secs(100), + 0, + 0, + header, + ); + + meta.0.internal.updated = SystemTime::UNIX_EPOCH + Duration::from_secs(1000); + meta.0.internal.fresh_until = SystemTime::UNIX_EPOCH + Duration::from_secs(1100); + + // Without epoch_override, fresh_sec should use updated as reference + let fresh_sec_without_override = meta.fresh_sec(); + assert_eq!(fresh_sec_without_override, 100); // 1100 - 1000 = 100 seconds + + // With epoch_override set to a later time (1050), fresh_sec should be calculated from that reference + let epoch_override = SystemTime::UNIX_EPOCH + Duration::from_secs(1050); + meta.set_epoch_override(epoch_override); + assert_eq!(meta.epoch_override(), Some(epoch_override)); + assert_eq!(meta.epoch(), epoch_override); + + let fresh_sec_with_override = meta.fresh_sec(); + // fresh_until - epoch_override = 1100 - 1050 = 50 seconds + assert_eq!(fresh_sec_with_override, 50); + + meta.remove_epoch_override(); + assert_eq!(meta.epoch_override(), None); + assert_eq!(meta.epoch(), meta.updated()); + assert_eq!(meta.fresh_sec(), 100); // back to normal calculation + } +} diff --git a/pingora-cache/src/predictor.rs b/pingora-cache/src/predictor.rs index 58f1315f..8c2f5a8f 100644 --- a/pingora-cache/src/predictor.rs +++ b/pingora-cache/src/predictor.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/put.rs b/pingora-cache/src/put.rs index dc390aa6..fbbbb70e 100644 --- a/pingora-cache/src/put.rs +++ b/pingora-cache/src/put.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/storage.rs b/pingora-cache/src/storage.rs index 06d008c2..5df1526d 100644 --- a/pingora-cache/src/storage.rs +++ b/pingora-cache/src/storage.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-cache/src/trace.rs b/pingora-cache/src/trace.rs index 60275f98..f27929a2 100644 --- a/pingora-cache/src/trace.rs +++ b/pingora-cache/src/trace.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/Cargo.toml b/pingora-core/Cargo.toml index 6149523e..03015d26 100644 --- a/pingora-core/Cargo.toml +++ b/pingora-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-core" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -19,15 +19,15 @@ name = "pingora_core" path = "src/lib.rs" [dependencies] -pingora-runtime = { version = "0.6.0", path = "../pingora-runtime" } -pingora-openssl = { version = "0.6.0", path = "../pingora-openssl", optional = true } -pingora-boringssl = { version = "0.6.0", path = "../pingora-boringssl", optional = true } -pingora-pool = { version = "0.6.0", path = "../pingora-pool" } -pingora-error = { version = "0.6.0", path = "../pingora-error" } -pingora-timeout = { version = "0.6.0", path = "../pingora-timeout" } -pingora-http = { version = "0.6.0", path = "../pingora-http" } -pingora-rustls = { version = "0.6.0", path = "../pingora-rustls", optional = true } -pingora-s2n = { version = "0.6.0", path = "../pingora-s2n", optional = true } +pingora-runtime = { version = "0.7.0", path = "../pingora-runtime" } +pingora-openssl = { version = "0.7.0", path = "../pingora-openssl", optional = true } +pingora-boringssl = { version = "0.7.0", path = "../pingora-boringssl", optional = true } +pingora-pool = { version = "0.7.0", path = "../pingora-pool" } +pingora-error = { version = "0.7.0", path = "../pingora-error" } +pingora-timeout = { version = "0.7.0", path = "../pingora-timeout" } +pingora-http = { version = "0.7.0", path = "../pingora-http" } +pingora-rustls = { version = "0.7.0", path = "../pingora-rustls", optional = true } +pingora-s2n = { version = "0.7.0", path = "../pingora-s2n", optional = true } bstr = { workspace = true } tokio = { workspace = true, features = ["net", "rt-multi-thread", "signal"] } tokio-stream = { workspace = true } @@ -71,7 +71,7 @@ zstd = "0" httpdate = "1" x509-parser = { version = "0.16.0", optional = true } ouroboros = { version = "0.18.4", optional = true } -lru = { version = "0.16.0", optional = true } +lru = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] daemonize = "0.5.0" diff --git a/pingora-core/examples/client_cert.rs b/pingora-core/examples/client_cert.rs index 8e406245..cbac46a1 100644 --- a/pingora-core/examples/client_cert.rs +++ b/pingora-core/examples/client_cert.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/apps/http_app.rs b/pingora-core/src/apps/http_app.rs index d2c59513..f511012c 100644 --- a/pingora-core/src/apps/http_app.rs +++ b/pingora-core/src/apps/http_app.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/apps/mod.rs b/pingora-core/src/apps/mod.rs index 019edbec..5702b19c 100644 --- a/pingora-core/src/apps/mod.rs +++ b/pingora-core/src/apps/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -61,7 +61,7 @@ pub trait ServerApp { #[derive(Default)] /// HTTP Server options that control how the server handles some transport types. pub struct HttpServerOptions { - /// Use HTTP/2 for plaintext. + /// Allow HTTP/2 for plaintext. pub h2c: bool, #[doc(hidden)] diff --git a/pingora-core/src/apps/prometheus_http_app.rs b/pingora-core/src/apps/prometheus_http_app.rs index 963d5a9e..ed8a217a 100644 --- a/pingora-core/src/apps/prometheus_http_app.rs +++ b/pingora-core/src/apps/prometheus_http_app.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/connectors/http/custom/mod.rs b/pingora-core/src/connectors/http/custom/mod.rs index bcce5bc4..e1e8a11d 100644 --- a/pingora-core/src/connectors/http/custom/mod.rs +++ b/pingora-core/src/connectors/http/custom/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/connectors/http/mod.rs b/pingora-core/src/connectors/http/mod.rs index 68a52078..603b9109 100644 --- a/pingora-core/src/connectors/http/mod.rs +++ b/pingora-core/src/connectors/http/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/connectors/http/v1.rs b/pingora-core/src/connectors/http/v1.rs index 36026a40..ebe52e64 100644 --- a/pingora-core/src/connectors/http/v1.rs +++ b/pingora-core/src/connectors/http/v1.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/connectors/http/v2.rs b/pingora-core/src/connectors/http/v2.rs index 9643374c..fb92b8a5 100644 --- a/pingora-core/src/connectors/http/v2.rs +++ b/pingora-core/src/connectors/http/v2.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -315,6 +315,9 @@ impl Connector { let maybe_conn = self .in_use_pool .get(reuse_hash) + // filter out closed, InUsePool does not have notify closed eviction like the idle pool + // and it's possible we get an in use connection that is closed and not yet released + .filter(|c| !c.is_closed()) .or_else(|| self.idle_pool.get(&reuse_hash)); if let Some(conn) = maybe_conn { let h2_stream = conn.spawn_stream().await?; @@ -366,14 +369,12 @@ impl Connector { }; let closed = conn.0.closed.clone(); let (notify_evicted, watch_use) = self.idle_pool.put(&meta, conn); - if let Some(to) = idle_timeout { - let pool = self.idle_pool.clone(); //clone the arc - let rt = pingora_runtime::current_handle(); - rt.spawn(async move { - pool.idle_timeout(&meta, to, notify_evicted, closed, watch_use) - .await; - }); - } + let pool = self.idle_pool.clone(); //clone the arc + let rt = pingora_runtime::current_handle(); + rt.spawn(async move { + pool.idle_timeout(&meta, idle_timeout, notify_evicted, closed, watch_use) + .await; + }); } else { self.in_use_pool.insert(reuse_hash, conn); drop(locked); diff --git a/pingora-core/src/connectors/l4.rs b/pingora-core/src/connectors/l4.rs index 1f072b1b..bd7439d4 100644 --- a/pingora-core/src/connectors/l4.rs +++ b/pingora-core/src/connectors/l4.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -314,8 +314,6 @@ mod tests { use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::io::AsyncWriteExt; - #[cfg(unix)] - use tokio::net::UnixListener; use tokio::time::sleep; /// Some of the tests below are flaky when making new connections to mock @@ -465,31 +463,20 @@ mod tests { } #[cfg(unix)] - const MOCK_UDS_PATH: &str = "/tmp/test_unix_connect_proxy.sock"; - - // one-off mock server - #[cfg(unix)] - async fn mock_connect_server() { - let _ = std::fs::remove_file(MOCK_UDS_PATH); - let listener = UnixListener::bind(MOCK_UDS_PATH).unwrap(); - if let Ok((mut stream, _addr)) = listener.accept().await { - stream.write_all(b"HTTP/1.1 200 OK\r\n\r\n").await.unwrap(); - // wait a bit so that the client can read - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - let _ = std::fs::remove_file(MOCK_UDS_PATH); - } - #[tokio::test(flavor = "multi_thread")] async fn test_connect_proxy_work() { - tokio::spawn(async { - mock_connect_server().await; - }); - // wait for the server to start - tokio::time::sleep(std::time::Duration::from_millis(100)).await; + use crate::connectors::test_utils; + + let socket_path = test_utils::unique_uds_path("connect_proxy_work"); + let (ready_rx, shutdown_tx, server_handle) = + test_utils::spawn_mock_uds_server(socket_path.clone(), b"HTTP/1.1 200 OK\r\n\r\n"); + + // Wait for the server to be ready + ready_rx.await.unwrap(); + let mut peer = HttpPeer::new("1.1.1.1:80".to_string(), false, "".to_string()); let mut path = PathBuf::new(); - path.push(MOCK_UDS_PATH); + path.push(&socket_path); peer.proxy = Some(Proxy { next_hop: path.into(), host: "1.1.1.1".into(), @@ -498,35 +485,27 @@ mod tests { }); let new_session = connect(&peer, None).await; assert!(new_session.is_ok()); - } - - #[cfg(unix)] - const MOCK_BAD_UDS_PATH: &str = "/tmp/test_unix_bad_connect_proxy.sock"; - // one-off mock bad proxy - // closes connection upon accepting - #[cfg(unix)] - async fn mock_connect_bad_server() { - let _ = std::fs::remove_file(MOCK_BAD_UDS_PATH); - let listener = UnixListener::bind(MOCK_BAD_UDS_PATH).unwrap(); - if let Ok((mut stream, _addr)) = listener.accept().await { - stream.shutdown().await.unwrap(); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - let _ = std::fs::remove_file(MOCK_BAD_UDS_PATH); + // Clean up + let _ = shutdown_tx.send(()); + server_handle.await.unwrap(); } #[cfg(unix)] #[tokio::test(flavor = "multi_thread")] async fn test_connect_proxy_conn_closed() { - tokio::spawn(async { - mock_connect_bad_server().await; - }); - // wait for the server to start - tokio::time::sleep(std::time::Duration::from_millis(100)).await; + use crate::connectors::test_utils; + + let socket_path = test_utils::unique_uds_path("connect_proxy_conn_closed"); + let (ready_rx, shutdown_tx, server_handle) = + test_utils::spawn_mock_uds_server_close_immediate(socket_path.clone()); + + // Wait for the server to be ready + ready_rx.await.unwrap(); + let mut peer = HttpPeer::new("1.1.1.1:80".to_string(), false, "".to_string()); let mut path = PathBuf::new(); - path.push(MOCK_BAD_UDS_PATH); + path.push(&socket_path); peer.proxy = Some(Proxy { next_hop: path.into(), host: "1.1.1.1".into(), @@ -537,6 +516,10 @@ mod tests { let err = new_session.unwrap_err(); assert_eq!(err.etype(), &ConnectionClosed); assert!(!err.retry()); + + // Clean up + let _ = shutdown_tx.send(()); + server_handle.await.unwrap(); } #[cfg(target_os = "linux")] diff --git a/pingora-core/src/connectors/mod.rs b/pingora-core/src/connectors/mod.rs index 1e6c08dc..3e3c1c46 100644 --- a/pingora-core/src/connectors/mod.rs +++ b/pingora-core/src/connectors/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -399,6 +399,86 @@ fn test_reusable_stream(stream: &mut Stream) -> bool { } } +/// Test utilities for creating mock acceptors. +#[cfg(all(test, unix))] +pub(crate) mod test_utils { + use tokio::io::AsyncWriteExt; + use tokio::net::UnixListener; + + /// Generates a unique socket path for testing to avoid conflicts when running in parallel + pub fn unique_uds_path(test_name: &str) -> String { + format!( + "/tmp/test_{test_name}_{:?}_{}.sock", + std::thread::current().id(), + std::process::id() + ) + } + + /// A mock UDS server that accepts one connection, sends data, and waits for shutdown signal + /// + /// Returns: (ready_rx, shutdown_tx, server_handle) + /// - ready_rx: Wait on this to know when server is ready to accept connections + /// - shutdown_tx: Send on this to tell server to shut down + /// - server_handle: Join handle for the server task + pub fn spawn_mock_uds_server( + socket_path: String, + response: &'static [u8], + ) -> ( + tokio::sync::oneshot::Receiver<()>, + tokio::sync::oneshot::Sender<()>, + tokio::task::JoinHandle<()>, + ) { + let (ready_tx, ready_rx) = tokio::sync::oneshot::channel(); + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + + let server_handle = tokio::spawn(async move { + let _ = std::fs::remove_file(&socket_path); + let listener = UnixListener::bind(&socket_path).unwrap(); + // Signal that the server is ready to accept connections + let _ = ready_tx.send(()); + + if let Ok((mut stream, _addr)) = listener.accept().await { + let _ = stream.write_all(response).await; + // Keep the connection open until the test tells us to shutdown + let _ = shutdown_rx.await; + } + let _ = std::fs::remove_file(&socket_path); + }); + + (ready_rx, shutdown_tx, server_handle) + } + + /// A mock UDS server that immediately closes connections (for testing error handling) + /// + /// Returns: (ready_rx, shutdown_tx, server_handle) + pub fn spawn_mock_uds_server_close_immediate( + socket_path: String, + ) -> ( + tokio::sync::oneshot::Receiver<()>, + tokio::sync::oneshot::Sender<()>, + tokio::task::JoinHandle<()>, + ) { + let (ready_tx, ready_rx) = tokio::sync::oneshot::channel(); + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + + let server_handle = tokio::spawn(async move { + let _ = std::fs::remove_file(&socket_path); + let listener = UnixListener::bind(&socket_path).unwrap(); + // Signal that the server is ready to accept connections + let _ = ready_tx.send(()); + + if let Ok((mut stream, _addr)) = listener.accept().await { + let _ = stream.shutdown().await; + // Wait for shutdown signal before cleaning up + let _ = shutdown_rx.await; + } + let _ = std::fs::remove_file(&socket_path); + }); + + (ready_rx, shutdown_tx, server_handle) + } +} + #[cfg(test)] #[cfg(feature = "any_tls")] mod tests { @@ -407,9 +487,6 @@ mod tests { use super::*; use crate::upstreams::peer::BasicPeer; - use tokio::io::AsyncWriteExt; - #[cfg(unix)] - use tokio::net::UnixListener; // 192.0.2.1 is effectively a black hole const BLACK_HOLE: &str = "192.0.2.1:79"; @@ -440,38 +517,34 @@ mod tests { assert!(reused); } - #[cfg(unix)] - const MOCK_UDS_PATH: &str = "/tmp/test_unix_transport_connector.sock"; - - // one-off mock server - #[cfg(unix)] - async fn mock_connect_server() { - let _ = std::fs::remove_file(MOCK_UDS_PATH); - let listener = UnixListener::bind(MOCK_UDS_PATH).unwrap(); - if let Ok((mut stream, _addr)) = listener.accept().await { - stream.write_all(b"it works!").await.unwrap(); - // wait a bit so that the client can read - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - let _ = std::fs::remove_file(MOCK_UDS_PATH); - } #[tokio::test(flavor = "multi_thread")] + #[cfg(unix)] async fn test_connect_uds() { - tokio::spawn(async { - mock_connect_server().await; - }); + let socket_path = test_utils::unique_uds_path("transport_connector"); + let (ready_rx, shutdown_tx, server_handle) = + test_utils::spawn_mock_uds_server(socket_path.clone(), b"it works!"); + + // Wait for the server to be ready before connecting + ready_rx.await.unwrap(); + // create a new service at /tmp let connector = TransportConnector::new(None); - let peer = BasicPeer::new_uds(MOCK_UDS_PATH).unwrap(); + let peer = BasicPeer::new_uds(&socket_path).unwrap(); // make a new connection to mock uds let mut stream = connector.new_stream(&peer).await.unwrap(); let mut buf = [0; 9]; let _ = stream.read(&mut buf).await.unwrap(); assert_eq!(&buf, b"it works!"); - connector.release_stream(stream, peer.reuse_hash(), None); - let (_, reused) = connector.get_stream(&peer).await.unwrap(); + // Test connection reuse by releasing and getting the stream back + connector.release_stream(stream, peer.reuse_hash(), None); + let (stream, reused) = connector.get_stream(&peer).await.unwrap(); assert!(reused); + + // Clean up: drop the stream, tell server to shutdown, and wait for it + drop(stream); + let _ = shutdown_tx.send(()); + server_handle.await.unwrap(); } async fn do_test_conn_timeout(conf: Option) { diff --git a/pingora-core/src/connectors/offload.rs b/pingora-core/src/connectors/offload.rs index 06fc0895..fe2d1c72 100644 --- a/pingora-core/src/connectors/offload.rs +++ b/pingora-core/src/connectors/offload.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs b/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs index c4b9246f..9bb3a5a6 100644 --- a/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs +++ b/pingora-core/src/connectors/tls/boringssl_openssl/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/connectors/tls/mod.rs b/pingora-core/src/connectors/tls/mod.rs index 4c41dfa5..c49be80b 100644 --- a/pingora-core/src/connectors/tls/mod.rs +++ b/pingora-core/src/connectors/tls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/connectors/tls/rustls/mod.rs b/pingora-core/src/connectors/tls/rustls/mod.rs index d4e3f995..ff375929 100644 --- a/pingora-core/src/connectors/tls/rustls/mod.rs +++ b/pingora-core/src/connectors/tls/rustls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ use pingora_error::{ use pingora_rustls::{ load_ca_file_into_store, load_certs_and_key_files, load_platform_certs_incl_env_into_store, version, CertificateDer, CertificateError, ClientConfig as RusTlsClientConfig, - DigitallySignedStruct, PrivateKeyDer, RootCertStore, RusTlsError, ServerName, SignatureScheme, - TlsConnector as RusTlsConnector, UnixTime, WebPkiServerVerifier, + DigitallySignedStruct, KeyLogFile, PrivateKeyDer, RootCertStore, RusTlsError, ServerName, + SignatureScheme, TlsConnector as RusTlsConnector, UnixTime, WebPkiServerVerifier, }; // Uses custom certificate verification from rustls's 'danger' module. @@ -81,7 +81,6 @@ impl TlsConnector { if let Some((cert, key)) = conf.cert_key_file.as_ref() { certs_key = load_certs_and_key_files(cert, key)?; } - // TODO: support SSLKEYLOGFILE } else { load_platform_certs_incl_env_into_store(&mut ca_certs)?; } @@ -94,7 +93,7 @@ impl TlsConnector { RusTlsClientConfig::builder_with_protocol_versions(&[&version::TLS12, &version::TLS13]) .with_root_certificates(ca_certs.clone()); - let config = match certs_key { + let mut config = match certs_key { Some((certs, key)) => { match builder.with_client_auth_cert(certs.clone(), key.clone_key()) { Ok(config) => config, @@ -108,6 +107,13 @@ impl TlsConnector { None => builder.with_no_client_auth(), }; + // Enable SSLKEYLOGFILE support for debugging TLS traffic + if let Some(options) = options.as_ref() { + if options.debug_ssl_keylog { + config.key_log = Arc::new(KeyLogFile::new()); + } + } + Ok(Connector { ctx: Arc::new(TlsConnector { config: Arc::new(config), @@ -161,10 +167,12 @@ where .with_root_certificates(Arc::clone(&tls_ctx.ca_certs)); debug!("added root ca certificates"); - let updated_config = builder.with_client_auth_cert(certs, private_key).or_err( + let mut updated_config = builder.with_client_auth_cert(certs, private_key).or_err( InvalidCert, "Failed to use peer cert/key to update Rustls config", )?; + // Preserve keylog setting from original config + updated_config.key_log = Arc::clone(&config.key_log); Some(updated_config) } }; @@ -252,8 +260,9 @@ where } } +#[allow(dead_code)] #[derive(Debug)] -enum VerificationMode { +pub enum VerificationMode { SkipHostname, SkipAll, Full, diff --git a/pingora-core/src/connectors/tls/s2n/mod.rs b/pingora-core/src/connectors/tls/s2n/mod.rs index 36f931d2..fbfdd7e7 100644 --- a/pingora-core/src/connectors/tls/s2n/mod.rs +++ b/pingora-core/src/connectors/tls/s2n/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/lib.rs b/pingora-core/src/lib.rs index 7551e046..1e1b5d56 100644 --- a/pingora-core/src/lib.rs +++ b/pingora-core/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/listeners/connection_filter.rs b/pingora-core/src/listeners/connection_filter.rs index c6da0641..10ae642f 100644 --- a/pingora-core/src/listeners/connection_filter.rs +++ b/pingora-core/src/listeners/connection_filter.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/listeners/l4.rs b/pingora-core/src/listeners/l4.rs index 739d0443..1fee7437 100644 --- a/pingora-core/src/listeners/l4.rs +++ b/pingora-core/src/listeners/l4.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/listeners/mod.rs b/pingora-core/src/listeners/mod.rs index 3f1642a7..e44f1735 100644 --- a/pingora-core/src/listeners/mod.rs +++ b/pingora-core/src/listeners/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs b/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs index 5506070c..d957cac4 100644 --- a/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs +++ b/pingora-core/src/listeners/tls/boringssl_openssl/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/listeners/tls/mod.rs b/pingora-core/src/listeners/tls/mod.rs index 887293b3..c345073e 100644 --- a/pingora-core/src/listeners/tls/mod.rs +++ b/pingora-core/src/listeners/tls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/listeners/tls/rustls/mod.rs b/pingora-core/src/listeners/tls/rustls/mod.rs index fb1817b0..d5a489f1 100644 --- a/pingora-core/src/listeners/tls/rustls/mod.rs +++ b/pingora-core/src/listeners/tls/rustls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/listeners/tls/s2n/mod.rs b/pingora-core/src/listeners/tls/s2n/mod.rs index 2598e829..ed689445 100644 --- a/pingora-core/src/listeners/tls/s2n/mod.rs +++ b/pingora-core/src/listeners/tls/s2n/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/modules/http/compression.rs b/pingora-core/src/modules/http/compression.rs index 1906bd66..fa64d3c1 100644 --- a/pingora-core/src/modules/http/compression.rs +++ b/pingora-core/src/modules/http/compression.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/modules/http/grpc_web.rs b/pingora-core/src/modules/http/grpc_web.rs index b248e233..fd1d4ad2 100644 --- a/pingora-core/src/modules/http/grpc_web.rs +++ b/pingora-core/src/modules/http/grpc_web.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/modules/http/mod.rs b/pingora-core/src/modules/http/mod.rs index d220e6b0..04084258 100644 --- a/pingora-core/src/modules/http/mod.rs +++ b/pingora-core/src/modules/http/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/modules/mod.rs b/pingora-core/src/modules/mod.rs index 359b9ef4..c4a1c4a6 100644 --- a/pingora-core/src/modules/mod.rs +++ b/pingora-core/src/modules/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/digest.rs b/pingora-core/src/protocols/digest.rs index f939bb1f..632d41ec 100644 --- a/pingora-core/src/protocols/digest.rs +++ b/pingora-core/src/protocols/digest.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/body_buffer.rs b/pingora-core/src/protocols/http/body_buffer.rs index 3de55b12..a122df20 100644 --- a/pingora-core/src/protocols/http/body_buffer.rs +++ b/pingora-core/src/protocols/http/body_buffer.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/bridge/grpc_web.rs b/pingora-core/src/protocols/http/bridge/grpc_web.rs index 63d19727..8a091d27 100644 --- a/pingora-core/src/protocols/http/bridge/grpc_web.rs +++ b/pingora-core/src/protocols/http/bridge/grpc_web.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/bridge/mod.rs b/pingora-core/src/protocols/http/bridge/mod.rs index fa1f58ca..6d295d0b 100644 --- a/pingora-core/src/protocols/http/bridge/mod.rs +++ b/pingora-core/src/protocols/http/bridge/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/client.rs b/pingora-core/src/protocols/http/client.rs index 7ea1a207..48810754 100644 --- a/pingora-core/src/protocols/http/client.rs +++ b/pingora-core/src/protocols/http/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/compression/brotli.rs b/pingora-core/src/protocols/http/compression/brotli.rs index c4bb36a5..fa8a3bae 100644 --- a/pingora-core/src/protocols/http/compression/brotli.rs +++ b/pingora-core/src/protocols/http/compression/brotli.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/compression/gzip.rs b/pingora-core/src/protocols/http/compression/gzip.rs index 46678df6..97f7b636 100644 --- a/pingora-core/src/protocols/http/compression/gzip.rs +++ b/pingora-core/src/protocols/http/compression/gzip.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/compression/mod.rs b/pingora-core/src/protocols/http/compression/mod.rs index 2f86efce..93dc97c1 100644 --- a/pingora-core/src/protocols/http/compression/mod.rs +++ b/pingora-core/src/protocols/http/compression/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/compression/zstd.rs b/pingora-core/src/protocols/http/compression/zstd.rs index b8a45b41..39465918 100644 --- a/pingora-core/src/protocols/http/compression/zstd.rs +++ b/pingora-core/src/protocols/http/compression/zstd.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/conditional_filter.rs b/pingora-core/src/protocols/http/conditional_filter.rs index 49daebc9..10aee2f2 100644 --- a/pingora-core/src/protocols/http/conditional_filter.rs +++ b/pingora-core/src/protocols/http/conditional_filter.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/custom/client.rs b/pingora-core/src/protocols/http/custom/client.rs index c1448d89..14c6b7d5 100644 --- a/pingora-core/src/protocols/http/custom/client.rs +++ b/pingora-core/src/protocols/http/custom/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/custom/mod.rs b/pingora-core/src/protocols/http/custom/mod.rs index 2488fd0a..af809e7d 100644 --- a/pingora-core/src/protocols/http/custom/mod.rs +++ b/pingora-core/src/protocols/http/custom/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/custom/server.rs b/pingora-core/src/protocols/http/custom/server.rs index 2b555dbc..0e06537e 100644 --- a/pingora-core/src/protocols/http/custom/server.rs +++ b/pingora-core/src/protocols/http/custom/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -98,6 +98,11 @@ pub trait Session: Send + Sync + Unpin + 'static { &mut self, ) -> Option> + Unpin + Send + Sync + 'static>>; + fn restore_custom_message_reader( + &mut self, + reader: Box> + Unpin + Send + Sync + 'static>, + ) -> Result<()>; + fn take_custom_message_writer(&mut self) -> Option>; fn restore_custom_message_writer(&mut self, writer: Box) -> Result<()>; @@ -252,6 +257,13 @@ impl Session for () { unreachable!("server session: get_custom_message_reader") } + fn restore_custom_message_reader( + &mut self, + _reader: Box> + Unpin + Send + Sync + 'static>, + ) -> Result<()> { + unreachable!("server session: get_custom_message_reader") + } + fn take_custom_message_writer(&mut self) -> Option> { unreachable!("server session: get_custom_message_writer") } diff --git a/pingora-core/src/protocols/http/date.rs b/pingora-core/src/protocols/http/date.rs index 87d49489..610c9386 100644 --- a/pingora-core/src/protocols/http/date.rs +++ b/pingora-core/src/protocols/http/date.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/error_resp.rs b/pingora-core/src/protocols/http/error_resp.rs index f802d4d0..e58f66fe 100644 --- a/pingora-core/src/protocols/http/error_resp.rs +++ b/pingora-core/src/protocols/http/error_resp.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/mod.rs b/pingora-core/src/protocols/http/mod.rs index a9ad158b..2588b085 100644 --- a/pingora-core/src/protocols/http/mod.rs +++ b/pingora-core/src/protocols/http/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/server.rs b/pingora-core/src/protocols/http/server.rs index 333eaf2b..4f8c9770 100644 --- a/pingora-core/src/protocols/http/server.rs +++ b/pingora-core/src/protocols/http/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/subrequest/body.rs b/pingora-core/src/protocols/http/subrequest/body.rs index acfef4b5..d936ca24 100644 --- a/pingora-core/src/protocols/http/subrequest/body.rs +++ b/pingora-core/src/protocols/http/subrequest/body.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/subrequest/dummy.rs b/pingora-core/src/protocols/http/subrequest/dummy.rs index 9df9c2cb..93973448 100644 --- a/pingora-core/src/protocols/http/subrequest/dummy.rs +++ b/pingora-core/src/protocols/http/subrequest/dummy.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/subrequest/server.rs b/pingora-core/src/protocols/http/subrequest/server.rs index d9e86adb..ffff8505 100644 --- a/pingora-core/src/protocols/http/subrequest/server.rs +++ b/pingora-core/src/protocols/http/subrequest/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/v1/body.rs b/pingora-core/src/protocols/http/v1/body.rs index 95476721..0c758159 100644 --- a/pingora-core/src/protocols/http/v1/body.rs +++ b/pingora-core/src/protocols/http/v1/body.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/v1/client.rs b/pingora-core/src/protocols/http/v1/client.rs index 31f9878a..390b5979 100644 --- a/pingora-core/src/protocols/http/v1/client.rs +++ b/pingora-core/src/protocols/http/v1/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -187,7 +187,17 @@ impl HttpSession { /// This function can be called multiple times, if the headers received are just informational /// headers. pub async fn read_response(&mut self) -> Result { - self.buf.clear(); + if self.preread_body.as_ref().is_none_or(|b| b.is_empty()) { + // preread_body is set after a completed valid response header is read + // if called multiple times (i.e. after informational responses), + // we want to parse the already read buffer bytes as more headers. + // (https://datatracker.ietf.org/doc/html/rfc9110#section-15.2 + // "A 1xx response is terminated by the end of the header section; + // it cannot contain content or trailers.") + // If this next read_response call completes successfully, + // self.buf will be reset to the last response + any body. + self.buf.clear(); + } let mut buf = BytesMut::with_capacity(INIT_HEADER_BUF_SIZE); let mut already_read: usize = 0; loop { @@ -201,12 +211,18 @@ impl HttpSession { ); } - let read_fut = self.underlying_stream.read_buf(&mut buf); - let read_result = match self.read_timeout { - Some(t) => timeout(t, read_fut) - .await - .map_err(|_| Error::explain(ReadTimedout, "while reading response headers"))?, - None => read_fut.await, + let preread = self.preread_body.take(); + let read_result = if let Some(preread) = preread.filter(|b| !b.is_empty()) { + buf.put_slice(preread.get(&self.buf)); + Ok(preread.len()) + } else { + let read_fut = self.underlying_stream.read_buf(&mut buf); + match self.read_timeout { + Some(t) => timeout(t, read_fut).await.map_err(|_| { + Error::explain(ReadTimedout, "while reading response headers") + })?, + None => read_fut.await, + } }; let n = match read_result { Ok(n) => match n { @@ -260,6 +276,9 @@ impl HttpSession { Some(resp.headers.len()), )?); + // TODO: enforce https://datatracker.ietf.org/doc/html/rfc9110#section-15.2 + // "Since HTTP/1.0 did not define any 1xx status codes, + // a server MUST NOT send a 1xx response to an HTTP/1.0 client." response_header.set_version(match resp.version { Some(1) => Version::HTTP_11, Some(0) => Version::HTTP_10, @@ -1164,6 +1183,93 @@ mod tests_stream { } } + #[tokio::test] + async fn read_informational_combined_with_final() { + init_log(); + let input = b"HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nServer: pingora\r\nContent-Length: 3\r\n\r\n"; + let body = b"abc"; + let mock_io = Builder::new().read(&input[..]).read(&body[..]).build(); + let mut http_stream = HttpSession::new(Box::new(mock_io)); + + // read 100 header first + let task = http_stream.read_response_task().await.unwrap(); + match task { + HttpTask::Header(h, eob) => { + assert_eq!(h.status, 100); + assert!(!eob); + } + _ => { + panic!("task should be header") + } + } + // read 200 header next + let task = http_stream.read_response_task().await.unwrap(); + match task { + HttpTask::Header(h, eob) => { + assert_eq!(h.status, 200); + assert!(!eob); + } + _ => { + panic!("task should be header") + } + } + // read body next + let task = http_stream.read_response_task().await.unwrap(); + match task { + HttpTask::Body(b, eob) => { + assert_eq!(b.unwrap(), &body[..]); + assert!(eob); + } + _ => { + panic!("task {task:?} should be body") + } + } + } + + #[tokio::test] + async fn read_informational_multiple_combined_with_final() { + init_log(); + let input = b"HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 103 Early Hints\r\n\r\nHTTP/1.1 204 No Content\r\nServer: pingora\r\n\r\n"; + let mock_io = Builder::new().read(&input[..]).build(); + let mut http_stream = HttpSession::new(Box::new(mock_io)); + + // read 100 header first + let task = http_stream.read_response_task().await.unwrap(); + match task { + HttpTask::Header(h, eob) => { + assert_eq!(h.status, 100); + assert!(!eob); + } + _ => { + panic!("task should be header") + } + } + + // then read 103 header + let task = http_stream.read_response_task().await.unwrap(); + match task { + HttpTask::Header(h, eob) => { + assert_eq!(h.status, 103); + assert!(!eob); + } + _ => { + panic!("task should be header") + } + } + + // finally read 200 header + let task = http_stream.read_response_task().await.unwrap(); + match task { + HttpTask::Header(h, eob) => { + assert_eq!(h.status, 204); + assert!(eob); + } + _ => { + panic!("task should be header") + } + } + } + #[tokio::test] async fn init_body_for_upgraded_req() { use crate::protocols::http::v1::body::BodyMode; diff --git a/pingora-core/src/protocols/http/v1/common.rs b/pingora-core/src/protocols/http/v1/common.rs index d4b3e6e6..adee99f4 100644 --- a/pingora-core/src/protocols/http/v1/common.rs +++ b/pingora-core/src/protocols/http/v1/common.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/v1/mod.rs b/pingora-core/src/protocols/http/v1/mod.rs index c819ee08..19602491 100644 --- a/pingora-core/src/protocols/http/v1/mod.rs +++ b/pingora-core/src/protocols/http/v1/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/v1/server.rs b/pingora-core/src/protocols/http/v1/server.rs index 0f0aa667..8184f9b7 100644 --- a/pingora-core/src/protocols/http/v1/server.rs +++ b/pingora-core/src/protocols/http/v1/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -119,7 +119,8 @@ impl HttpSession { digest, min_send_rate: None, ignore_info_resp: false, - close_on_response_before_downstream_finish: false, + // default on to avoid rejecting requests after body as pipelined + close_on_response_before_downstream_finish: true, } } @@ -472,7 +473,10 @@ impl HttpSession { } } - if self.close_on_response_before_downstream_finish && !self.is_body_done() { + // if body unfinished, or request header was not finished reading + if self.close_on_response_before_downstream_finish + && (self.request_header.is_none() || !self.is_body_done()) + { debug!("set connection close before downstream finish"); self.set_keepalive(None); } @@ -1563,6 +1567,15 @@ mod tests_stream { assert_eq!(&InvalidHTTPHeader, res.unwrap_err().etype()); } + #[tokio::test] + async fn read_invalid_header_end() { + let input = b"POST / HTTP/1.1\r\nHost: pingora.org\r\nContent-Length: 3\r\r\nConnection: keep-alive\r\n\r\nabc"; + let mock_io = Builder::new().read(&input[..]).build(); + let mut http_stream = HttpSession::new(Box::new(mock_io)); + let res = http_stream.read_request().await; + assert_eq!(&InvalidHTTPHeader, res.unwrap_err().etype()); + } + async fn build_req(upgrade: &str, conn: &str) -> HttpSession { let input = format!("GET / HTTP/1.1\r\nHost: pingora.org\r\nUpgrade: {upgrade}\r\nConnection: {conn}\r\n\r\n"); let mock_io = Builder::new().read(input.as_bytes()).build(); @@ -1671,9 +1684,11 @@ mod tests_stream { #[tokio::test] async fn write() { - let wire = b"HTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; - let mock_io = Builder::new().write(wire).build(); + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; + let write_expected = b"HTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; + let mock_io = Builder::new().read(read_wire).write(write_expected).build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let mut new_response = ResponseHeader::build(StatusCode::OK, None).unwrap(); new_response.append_header("Foo", "Bar").unwrap(); http_stream.update_resp_headers = false; @@ -1685,9 +1700,11 @@ mod tests_stream { #[tokio::test] async fn write_custom_reason() { - let wire = b"HTTP/1.1 200 Just Fine\r\nFoo: Bar\r\n\r\n"; - let mock_io = Builder::new().write(wire).build(); + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; + let write_expected = b"HTTP/1.1 200 Just Fine\r\nFoo: Bar\r\n\r\n"; + let mock_io = Builder::new().read(read_wire).write(write_expected).build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let mut new_response = ResponseHeader::build(StatusCode::OK, None).unwrap(); new_response.set_reason_phrase(Some("Just Fine")).unwrap(); new_response.append_header("Foo", "Bar").unwrap(); @@ -1700,9 +1717,11 @@ mod tests_stream { #[tokio::test] async fn write_informational() { - let wire = b"HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; - let mock_io = Builder::new().write(wire).build(); + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; + let write_expected = b"HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; + let mock_io = Builder::new().read(read_wire).write(write_expected).build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let response_100 = ResponseHeader::build(StatusCode::CONTINUE, None).unwrap(); http_stream .write_response_header_ref(&response_100) @@ -1719,11 +1738,13 @@ mod tests_stream { #[tokio::test] async fn write_informational_ignored() { - let wire = b"HTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; - let mock_io = Builder::new().write(wire).build(); + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; + let write_expected = b"HTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; + let mock_io = Builder::new().read(read_wire).write(write_expected).build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); // ignore the 100 Continue http_stream.ignore_info_resp = true; + http_stream.read_request().await.unwrap(); let response_100 = ResponseHeader::build(StatusCode::CONTINUE, None).unwrap(); http_stream .write_response_header_ref(&response_100) @@ -1788,10 +1809,16 @@ mod tests_stream { #[tokio::test] async fn write_101_switching_protocol() { + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; let wire = b"HTTP/1.1 101 Switching Protocols\r\nFoo: Bar\r\n\r\n"; let wire_body = b"nPAYLOAD"; - let mock_io = Builder::new().write(wire).write(wire_body).build(); + let mock_io = Builder::new() + .read(read_wire) + .write(wire) + .write(wire_body) + .build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let mut response_101 = ResponseHeader::build(StatusCode::SWITCHING_PROTOCOLS, None).unwrap(); response_101.append_header("Foo", "Bar").unwrap(); @@ -1813,10 +1840,16 @@ mod tests_stream { #[tokio::test] async fn write_body_cl() { + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; let wire_header = b"HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\n"; let wire_body = b"a"; - let mock_io = Builder::new().write(wire_header).write(wire_body).build(); + let mock_io = Builder::new() + .read(read_wire) + .write(wire_header) + .write(wire_body) + .build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let mut new_response = ResponseHeader::build(StatusCode::OK, None).unwrap(); new_response.append_header("Content-Length", "1").unwrap(); http_stream.update_resp_headers = false; @@ -1836,10 +1869,16 @@ mod tests_stream { #[tokio::test] async fn write_body_http10() { + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; let wire_header = b"HTTP/1.1 200 OK\r\n\r\n"; let wire_body = b"a"; - let mock_io = Builder::new().write(wire_header).write(wire_body).build(); + let mock_io = Builder::new() + .read(read_wire) + .write(wire_header) + .write(wire_body) + .build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let new_response = ResponseHeader::build(StatusCode::OK, None).unwrap(); http_stream.update_resp_headers = false; http_stream @@ -1855,15 +1894,18 @@ mod tests_stream { #[tokio::test] async fn write_body_chunk() { + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; let wire_header = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"; let wire_body = b"1\r\na\r\n"; let wire_end = b"0\r\n\r\n"; let mock_io = Builder::new() + .read(read_wire) .write(wire_header) .write(wire_body) .write(wire_end) .build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let mut new_response = ResponseHeader::build(StatusCode::OK, None).unwrap(); new_response .append_header("Transfer-Encoding", "chunked") @@ -1934,9 +1976,11 @@ mod tests_stream { #[tokio::test] async fn test_write_body_buf() { - let wire = b"HTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; - let mock_io = Builder::new().write(wire).build(); + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; + let write_expected = b"HTTP/1.1 200 OK\r\nFoo: Bar\r\n\r\n"; + let mock_io = Builder::new().read(read_wire).write(write_expected).build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); let mut new_response = ResponseHeader::build(StatusCode::OK, None).unwrap(); new_response.append_header("Foo", "Bar").unwrap(); http_stream.update_resp_headers = false; @@ -1951,14 +1995,17 @@ mod tests_stream { #[tokio::test] #[should_panic(expected = "There is still data left to write.")] async fn test_write_body_buf_write_timeout() { + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; let wire1 = b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\n"; let wire2 = b"abc"; let mock_io = Builder::new() + .read(read_wire) .write(wire1) .wait(Duration::from_millis(500)) .write(wire2) .build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); http_stream.write_timeout = Some(Duration::from_millis(100)); let mut new_response = ResponseHeader::build(StatusCode::OK, None).unwrap(); new_response.append_header("Content-Length", "3").unwrap(); @@ -1974,9 +2021,11 @@ mod tests_stream { #[tokio::test] async fn test_write_continue_resp() { - let wire = b"HTTP/1.1 100 Continue\r\n\r\n"; - let mock_io = Builder::new().write(wire).build(); + let read_wire = b"GET / HTTP/1.1\r\n\r\n"; + let write_expected = b"HTTP/1.1 100 Continue\r\n\r\n"; + let mock_io = Builder::new().read(read_wire).write(write_expected).build(); let mut http_stream = HttpSession::new(Box::new(mock_io)); + http_stream.read_request().await.unwrap(); http_stream.write_continue_response().await.unwrap(); } diff --git a/pingora-core/src/protocols/http/v2/client.rs b/pingora-core/src/protocols/http/v2/client.rs index f40efbc1..dd3a14d4 100644 --- a/pingora-core/src/protocols/http/v2/client.rs +++ b/pingora-core/src/protocols/http/v2/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/v2/mod.rs b/pingora-core/src/protocols/http/v2/mod.rs index a588f4bd..01711807 100644 --- a/pingora-core/src/protocols/http/v2/mod.rs +++ b/pingora-core/src/protocols/http/v2/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/http/v2/server.rs b/pingora-core/src/protocols/http/v2/server.rs index 085a53d2..d4b1d423 100644 --- a/pingora-core/src/protocols/http/v2/server.rs +++ b/pingora-core/src/protocols/http/v2/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/l4/ext.rs b/pingora-core/src/protocols/l4/ext.rs index a380932a..9a632e96 100644 --- a/pingora-core/src/protocols/l4/ext.rs +++ b/pingora-core/src/protocols/l4/ext.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/l4/listener.rs b/pingora-core/src/protocols/l4/listener.rs index 88f5fe85..7d00005e 100644 --- a/pingora-core/src/protocols/l4/listener.rs +++ b/pingora-core/src/protocols/l4/listener.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/l4/mod.rs b/pingora-core/src/protocols/l4/mod.rs index 834b63d3..7e24cd88 100644 --- a/pingora-core/src/protocols/l4/mod.rs +++ b/pingora-core/src/protocols/l4/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/l4/socket.rs b/pingora-core/src/protocols/l4/socket.rs index 258acc4f..46decd2f 100644 --- a/pingora-core/src/protocols/l4/socket.rs +++ b/pingora-core/src/protocols/l4/socket.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/l4/stream.rs b/pingora-core/src/protocols/l4/stream.rs index 67054b1e..59ec3f60 100644 --- a/pingora-core/src/protocols/l4/stream.rs +++ b/pingora-core/src/protocols/l4/stream.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -201,7 +201,7 @@ impl AsyncRead for RawStreamWrapper { RawStream::Tcp(s) => Pin::new_unchecked(s).poll_read(cx, buf), #[cfg(unix)] RawStream::Unix(s) => Pin::new_unchecked(s).poll_read(cx, buf), - RawStream::Virtual(s) => return Pin::new_unchecked(s).poll_read(cx, buf), + RawStream::Virtual(s) => Pin::new_unchecked(s).poll_read(cx, buf), } } } diff --git a/pingora-core/src/protocols/mod.rs b/pingora-core/src/protocols/mod.rs index d3bd99b8..1f8cce03 100644 --- a/pingora-core/src/protocols/mod.rs +++ b/pingora-core/src/protocols/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/raw_connect.rs b/pingora-core/src/protocols/raw_connect.rs index b900f7f7..80158edc 100644 --- a/pingora-core/src/protocols/raw_connect.rs +++ b/pingora-core/src/protocols/raw_connect.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/boringssl_openssl/client.rs b/pingora-core/src/protocols/tls/boringssl_openssl/client.rs index 6fa12814..4e5bded4 100644 --- a/pingora-core/src/protocols/tls/boringssl_openssl/client.rs +++ b/pingora-core/src/protocols/tls/boringssl_openssl/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs b/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs index cb6876c3..7d2c1e2b 100644 --- a/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs +++ b/pingora-core/src/protocols/tls/boringssl_openssl/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/boringssl_openssl/server.rs b/pingora-core/src/protocols/tls/boringssl_openssl/server.rs index f3f641a6..bd14ea70 100644 --- a/pingora-core/src/protocols/tls/boringssl_openssl/server.rs +++ b/pingora-core/src/protocols/tls/boringssl_openssl/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/boringssl_openssl/stream.rs b/pingora-core/src/protocols/tls/boringssl_openssl/stream.rs index 153bb4c9..894244c0 100644 --- a/pingora-core/src/protocols/tls/boringssl_openssl/stream.rs +++ b/pingora-core/src/protocols/tls/boringssl_openssl/stream.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/digest.rs b/pingora-core/src/protocols/tls/digest.rs index 7f353108..58ecf3b6 100644 --- a/pingora-core/src/protocols/tls/digest.rs +++ b/pingora-core/src/protocols/tls/digest.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/mod.rs b/pingora-core/src/protocols/tls/mod.rs index dc6d5287..ee613bec 100644 --- a/pingora-core/src/protocols/tls/mod.rs +++ b/pingora-core/src/protocols/tls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/noop_tls/mod.rs b/pingora-core/src/protocols/tls/noop_tls/mod.rs index b909a3b2..d7632e13 100644 --- a/pingora-core/src/protocols/tls/noop_tls/mod.rs +++ b/pingora-core/src/protocols/tls/noop_tls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/rustls/client.rs b/pingora-core/src/protocols/tls/rustls/client.rs index 7ff701ab..a8e00c41 100644 --- a/pingora-core/src/protocols/tls/rustls/client.rs +++ b/pingora-core/src/protocols/tls/rustls/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/rustls/mod.rs b/pingora-core/src/protocols/tls/rustls/mod.rs index f8bce5f6..c7c81fc8 100644 --- a/pingora-core/src/protocols/tls/rustls/mod.rs +++ b/pingora-core/src/protocols/tls/rustls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/rustls/server.rs b/pingora-core/src/protocols/tls/rustls/server.rs index 35fe6d2a..4367f75a 100644 --- a/pingora-core/src/protocols/tls/rustls/server.rs +++ b/pingora-core/src/protocols/tls/rustls/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/rustls/stream.rs b/pingora-core/src/protocols/tls/rustls/stream.rs index af144afb..f2a0ddae 100644 --- a/pingora-core/src/protocols/tls/rustls/stream.rs +++ b/pingora-core/src/protocols/tls/rustls/stream.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/s2n/client.rs b/pingora-core/src/protocols/tls/s2n/client.rs index 3b7c2858..544a6790 100644 --- a/pingora-core/src/protocols/tls/s2n/client.rs +++ b/pingora-core/src/protocols/tls/s2n/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/s2n/mod.rs b/pingora-core/src/protocols/tls/s2n/mod.rs index 0d78cb79..6118100c 100644 --- a/pingora-core/src/protocols/tls/s2n/mod.rs +++ b/pingora-core/src/protocols/tls/s2n/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/s2n/server.rs b/pingora-core/src/protocols/tls/s2n/server.rs index bde5c927..a8498f5d 100644 --- a/pingora-core/src/protocols/tls/s2n/server.rs +++ b/pingora-core/src/protocols/tls/s2n/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/tls/s2n/stream.rs b/pingora-core/src/protocols/tls/s2n/stream.rs index 96790be9..059718ea 100644 --- a/pingora-core/src/protocols/tls/s2n/stream.rs +++ b/pingora-core/src/protocols/tls/s2n/stream.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/protocols/windows.rs b/pingora-core/src/protocols/windows.rs index 10d6ce70..37c9e6fc 100644 --- a/pingora-core/src/protocols/windows.rs +++ b/pingora-core/src/protocols/windows.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/server/configuration/mod.rs b/pingora-core/src/server/configuration/mod.rs index b30333e0..020c90fb 100644 --- a/pingora-core/src/server/configuration/mod.rs +++ b/pingora-core/src/server/configuration/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -110,6 +110,12 @@ pub struct ServerConf { /// /// This setting is a fail-safe and defaults to 16. pub max_retries: usize, + /// Maximum number of retries for upgrade socket connect and accept operations. + /// This controls how many times send_fds_to will retry connecting and how many times + /// get_fds_from will retry accepting during graceful upgrades. + /// The retry interval is 1 second between attempts. + /// If not set, defaults to 5 retries. + pub upgrade_sock_connect_accept_max_retries: Option, } impl Default for ServerConf { @@ -137,6 +143,7 @@ impl Default for ServerConf { grace_period_seconds: None, graceful_shutdown_timeout_seconds: None, max_retries: DEFAULT_MAX_RETRIES, + upgrade_sock_connect_accept_max_retries: None, } } } @@ -303,6 +310,7 @@ mod tests { grace_period_seconds: None, graceful_shutdown_timeout_seconds: None, max_retries: 1, + upgrade_sock_connect_accept_max_retries: None, }; // cargo test -- --nocapture not_a_test_i_cannot_write_yaml_by_hand println!("{}", conf.to_yaml()); diff --git a/pingora-core/src/server/daemon.rs b/pingora-core/src/server/daemon.rs index c45a5eeb..7381fc93 100644 --- a/pingora-core/src/server/daemon.rs +++ b/pingora-core/src/server/daemon.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/server/mod.rs b/pingora-core/src/server/mod.rs index b750c7b2..ef4515c6 100644 --- a/pingora-core/src/server/mod.rs +++ b/pingora-core/src/server/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/server/transfer_fd/mod.rs b/pingora-core/src/server/transfer_fd/mod.rs index 3fb7259b..c851eb48 100644 --- a/pingora-core/src/server/transfer_fd/mod.rs +++ b/pingora-core/src/server/transfer_fd/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -68,7 +68,7 @@ impl Fds { let (vec_key, vec_fds) = self.serialize(); let mut ser_buf: [u8; 2048] = [0; 2048]; let ser_key_size = serialize_vec_string(&vec_key, &mut ser_buf); - send_fds_to(vec_fds, &ser_buf[..ser_key_size], path) + send_fds_to(vec_fds, &ser_buf[..ser_key_size], path, None) } pub fn get_from_sock

(&mut self, path: &P) -> Result<(), Error> @@ -76,7 +76,7 @@ impl Fds { P: ?Sized + NixPath + std::fmt::Display, { let mut de_buf: [u8; 2048] = [0; 2048]; - let (fds, bytes) = get_fds_from(path, &mut de_buf)?; + let (fds, bytes) = get_fds_from(path, &mut de_buf, None)?; let keys = deserialize_vec_string(&de_buf[..bytes])?; self.deserialize(keys, fds); Ok(()) @@ -97,10 +97,15 @@ fn deserialize_vec_string(buf: &[u8]) -> Result, Error> { } #[cfg(target_os = "linux")] -pub fn get_fds_from

(path: &P, payload: &mut [u8]) -> Result<(Vec, usize), Error> +pub fn get_fds_from

( + path: &P, + payload: &mut [u8], + max_retry: Option, +) -> Result<(Vec, usize), Error> where P: ?Sized + NixPath + std::fmt::Display, { + let max_retry = max_retry.unwrap_or(MAX_RETRY); const MAX_FDS: usize = 32; let listen_fd = socket::socket( @@ -135,7 +140,7 @@ where socket::listen(listen_fd, 8).unwrap(); - let fd = match accept_with_retry(listen_fd) { + let fd = match accept_with_retry_timeout(listen_fd, max_retry) { Ok(fd) => fd, Err(e) => { error!("Giving up reading socket from: {path}, error: {e:?}"); @@ -175,7 +180,11 @@ where } #[cfg(not(target_os = "linux"))] -pub fn get_fds_from

(_path: &P, _payload: &mut [u8]) -> Result<(Vec, usize), Error> +pub fn get_fds_from

( + _path: &P, + _payload: &mut [u8], + _max_retry: Option, +) -> Result<(Vec, usize), Error> where P: ?Sized + NixPath + std::fmt::Display, { @@ -189,13 +198,13 @@ const MAX_RETRY: usize = 5; const RETRY_INTERVAL: time::Duration = time::Duration::from_secs(1); #[cfg(target_os = "linux")] -fn accept_with_retry(listen_fd: i32) -> Result { +fn accept_with_retry_timeout(listen_fd: i32, max_retry: usize) -> Result { let mut retried = 0; loop { match socket::accept(listen_fd) { Ok(fd) => return Ok(fd), Err(e) => { - if retried > MAX_RETRY { + if retried > max_retry { return Err(e); } match e { @@ -217,10 +226,16 @@ fn accept_with_retry(listen_fd: i32) -> Result { } #[cfg(target_os = "linux")] -pub fn send_fds_to

(fds: Vec, payload: &[u8], path: &P) -> Result +pub fn send_fds_to

( + fds: Vec, + payload: &[u8], + path: &P, + max_retry: Option, +) -> Result where P: ?Sized + NixPath + std::fmt::Display, { + let max_retry = max_retry.unwrap_or(MAX_RETRY); const MAX_NONBLOCKING_POLLS: usize = 20; const NONBLOCKING_POLL_INTERVAL: time::Duration = time::Duration::from_millis(500); @@ -245,10 +260,10 @@ where Errno::ENOENT | Errno::ECONNREFUSED | Errno::EACCES => { /*the server is not ready yet*/ retried += 1; - if retried > MAX_RETRY { + if retried > max_retry { error!( "Max retry: {} reached. Giving up sending socket to: {}, error: {:?}", - MAX_RETRY, path, e + max_retry, path, e ); break Err(e); } @@ -317,7 +332,12 @@ where } #[cfg(not(target_os = "linux"))] -pub fn send_fds_to

(_fds: Vec, _payload: &[u8], _path: &P) -> Result +pub fn send_fds_to

( + _fds: Vec, + _payload: &[u8], + _path: &P, + _max_retry: Option, +) -> Result where P: ?Sized + NixPath + std::fmt::Display, { @@ -386,7 +406,8 @@ mod tests { // receiver need to start in another thread since it is blocking let child = thread::spawn(move || { let mut buf: [u8; 32] = [0; 32]; - let (fds, bytes) = get_fds_from("/tmp/pingora_fds_receive.sock", &mut buf).unwrap(); + let (fds, bytes) = + get_fds_from("/tmp/pingora_fds_receive.sock", &mut buf, None).unwrap(); debug!("{:?}", fds); assert_eq!(1, fds.len()); assert_eq!(32, bytes); @@ -396,7 +417,7 @@ mod tests { let fds = vec![dumb_fd]; let buf: [u8; 128] = [1; 128]; - match send_fds_to(fds, &buf, "/tmp/pingora_fds_receive.sock") { + match send_fds_to(fds, &buf, "/tmp/pingora_fds_receive.sock", None) { Ok(sent) => { assert!(sent > 0); } @@ -443,4 +464,67 @@ mod tests { fds.send_to_sock("/tmp/pingora_fds_receive2.sock").unwrap(); child.join().unwrap(); } + + #[test] + fn test_send_fds_to_respects_configurable_timeout() { + init_log(); + use std::time::Instant; + + let dumb_fd = socket::socket( + AddressFamily::Unix, + SockType::Stream, + SockFlag::empty(), + None, + ) + .unwrap(); + + let fds = vec![dumb_fd]; + let buf: [u8; 32] = [1; 32]; + + // Try to send with a custom max_retries of 2 + let start = Instant::now(); + let result = send_fds_to(fds, &buf, "/tmp/pingora_test_config_send.sock", Some(2)); + let elapsed = start.elapsed(); + + // Should fail after 2 retries with RETRY_INTERVAL (1 second) between each + // Total time should be approximately 2 seconds + assert!(result.is_err()); + assert!( + elapsed.as_secs() >= 2, + "Expected at least 2 seconds, got {:?}", + elapsed + ); + assert!( + elapsed.as_secs() < 4, + "Expected less than 4 seconds, got {:?}", + elapsed + ); + } + + #[test] + fn test_get_fds_from_respects_configurable_timeout() { + init_log(); + use std::time::Instant; + + let mut buf: [u8; 32] = [0; 32]; + + // Try to receive with a custom max_retries of 2 + let start = Instant::now(); + let result = get_fds_from("/tmp/pingora_test_config_receive.sock", &mut buf, Some(2)); + let elapsed = start.elapsed(); + + // Should fail after 2 retries with RETRY_INTERVAL (1 second) between each + // Total time should be approximately 2 seconds + assert!(result.is_err()); + assert!( + elapsed.as_secs() >= 2, + "Expected at least 2 seconds, got {:?}", + elapsed + ); + assert!( + elapsed.as_secs() < 4, + "Expected less than 4 seconds, got {:?}", + elapsed + ); + } } diff --git a/pingora-core/src/services/background.rs b/pingora-core/src/services/background.rs index 14582334..a8f439a2 100644 --- a/pingora-core/src/services/background.rs +++ b/pingora-core/src/services/background.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/services/listening.rs b/pingora-core/src/services/listening.rs index b5c04dd1..c130d324 100644 --- a/pingora-core/src/services/listening.rs +++ b/pingora-core/src/services/listening.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/services/mod.rs b/pingora-core/src/services/mod.rs index 51bda994..6bb6703e 100644 --- a/pingora-core/src/services/mod.rs +++ b/pingora-core/src/services/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/upstreams/mod.rs b/pingora-core/src/upstreams/mod.rs index 2348bc85..b66fc26a 100644 --- a/pingora-core/src/upstreams/mod.rs +++ b/pingora-core/src/upstreams/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/upstreams/peer.rs b/pingora-core/src/upstreams/peer.rs index f536ce78..3ff6c0ab 100644 --- a/pingora-core/src/upstreams/peer.rs +++ b/pingora-core/src/upstreams/peer.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -642,6 +642,17 @@ impl HttpPeer { } } + /// Create a new [`HttpPeer`] with client certificate and key for mutual TLS. + pub fn new_mtls( + address: A, + sni: String, + client_cert_key: Arc, + ) -> Self { + let mut peer = Self::new(address, true, sni); + peer.client_cert_key = Some(client_cert_key); + peer + } + fn peer_hash(&self) -> u64 { let mut hasher = AHasher::default(); self.hash(&mut hasher); diff --git a/pingora-core/src/utils/mod.rs b/pingora-core/src/utils/mod.rs index 2479c0b7..66ad444e 100644 --- a/pingora-core/src/utils/mod.rs +++ b/pingora-core/src/utils/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/utils/tls/boringssl_openssl.rs b/pingora-core/src/utils/tls/boringssl_openssl.rs index f78d5aeb..1f18adfb 100644 --- a/pingora-core/src/utils/tls/boringssl_openssl.rs +++ b/pingora-core/src/utils/tls/boringssl_openssl.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/utils/tls/mod.rs b/pingora-core/src/utils/tls/mod.rs index 887293b3..c345073e 100644 --- a/pingora-core/src/utils/tls/mod.rs +++ b/pingora-core/src/utils/tls/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/utils/tls/rustls.rs b/pingora-core/src/utils/tls/rustls.rs index d336e1fe..429b3724 100644 --- a/pingora-core/src/utils/tls/rustls.rs +++ b/pingora-core/src/utils/tls/rustls.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/src/utils/tls/s2n.rs b/pingora-core/src/utils/tls/s2n.rs index f52d86b1..4dffd32b 100644 --- a/pingora-core/src/utils/tls/s2n.rs +++ b/pingora-core/src/utils/tls/s2n.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/tests/server_phase_fastshutdown.rs b/pingora-core/tests/server_phase_fastshutdown.rs index 83eb3e9b..def35552 100644 --- a/pingora-core/tests/server_phase_fastshutdown.rs +++ b/pingora-core/tests/server_phase_fastshutdown.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/tests/server_phase_gracefulshutdown.rs b/pingora-core/tests/server_phase_gracefulshutdown.rs index 7c231e93..9d123f1e 100644 --- a/pingora-core/tests/server_phase_gracefulshutdown.rs +++ b/pingora-core/tests/server_phase_gracefulshutdown.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/tests/test_basic.rs b/pingora-core/tests/test_basic.rs index 60f95026..0c9f87f9 100644 --- a/pingora-core/tests/test_basic.rs +++ b/pingora-core/tests/test_basic.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-core/tests/utils/mod.rs b/pingora-core/tests/utils/mod.rs index 7062b349..a5016c0b 100644 --- a/pingora-core/tests/utils/mod.rs +++ b/pingora-core/tests/utils/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-error/Cargo.toml b/pingora-error/Cargo.toml index aec7939d..f69c4d7b 100644 --- a/pingora-error/Cargo.toml +++ b/pingora-error/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-error" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" diff --git a/pingora-error/src/immut_str.rs b/pingora-error/src/immut_str.rs index a03ef353..a9e1b6da 100644 --- a/pingora-error/src/immut_str.rs +++ b/pingora-error/src/immut_str.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-error/src/lib.rs b/pingora-error/src/lib.rs index a0b06f3b..c561bccf 100644 --- a/pingora-error/src/lib.rs +++ b/pingora-error/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-header-serde/Cargo.toml b/pingora-header-serde/Cargo.toml index 8c25636e..c58781ea 100644 --- a/pingora-header-serde/Cargo.toml +++ b/pingora-header-serde/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-header-serde" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -27,6 +27,6 @@ zstd-safe = { version = "7.1.0", features = ["std"] } http = { workspace = true } bytes = { workspace = true } httparse = { workspace = true } -pingora-error = { version = "0.6.0", path = "../pingora-error" } -pingora-http = { version = "0.6.0", path = "../pingora-http" } +pingora-error = { version = "0.7.0", path = "../pingora-error" } +pingora-http = { version = "0.7.0", path = "../pingora-http" } thread_local = "1.0" diff --git a/pingora-header-serde/src/dict.rs b/pingora-header-serde/src/dict.rs index 792698c1..3fb788d4 100644 --- a/pingora-header-serde/src/dict.rs +++ b/pingora-header-serde/src/dict.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-header-serde/src/lib.rs b/pingora-header-serde/src/lib.rs index e79b99c8..71122bf3 100644 --- a/pingora-header-serde/src/lib.rs +++ b/pingora-header-serde/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-header-serde/src/thread_zstd.rs b/pingora-header-serde/src/thread_zstd.rs index 97742011..4510d2b4 100644 --- a/pingora-header-serde/src/thread_zstd.rs +++ b/pingora-header-serde/src/thread_zstd.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-header-serde/src/trainer.rs b/pingora-header-serde/src/trainer.rs index 9e0ac5dc..aa016d45 100644 --- a/pingora-header-serde/src/trainer.rs +++ b/pingora-header-serde/src/trainer.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-http/Cargo.toml b/pingora-http/Cargo.toml index 459b206e..f3efc5ae 100644 --- a/pingora-http/Cargo.toml +++ b/pingora-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-http" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] http = { workspace = true } bytes = { workspace = true } -pingora-error = { version = "0.6.0", path = "../pingora-error" } +pingora-error = { version = "0.7.0", path = "../pingora-error" } [features] default = [] diff --git a/pingora-http/src/case_header_name.rs b/pingora-http/src/case_header_name.rs index 7cda091d..28d62c27 100644 --- a/pingora-http/src/case_header_name.rs +++ b/pingora-http/src/case_header_name.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-http/src/lib.rs b/pingora-http/src/lib.rs index 9e8958f8..954be81b 100644 --- a/pingora-http/src/lib.rs +++ b/pingora-http/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-ketama/Cargo.toml b/pingora-ketama/Cargo.toml index c94e2728..812aa666 100644 --- a/pingora-ketama/Cargo.toml +++ b/pingora-ketama/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-ketama" -version = "0.6.0" +version = "0.7.0" description = "Rust port of the nginx consistent hash function" authors = ["Pingora Team "] license = "Apache-2.0" @@ -31,4 +31,4 @@ harness = false [features] heap-prof = [] -v2 = ["i_key_sort"] \ No newline at end of file +v2 = ["i_key_sort"] diff --git a/pingora-ketama/src/lib.rs b/pingora-ketama/src/lib.rs index f27beea0..335501b1 100644 --- a/pingora-ketama/src/lib.rs +++ b/pingora-ketama/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-ketama/tests/backwards_compat.rs b/pingora-ketama/tests/backwards_compat.rs index 5c58bea9..3224cf42 100644 --- a/pingora-ketama/tests/backwards_compat.rs +++ b/pingora-ketama/tests/backwards_compat.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-ketama/tests/old_version/mod.rs b/pingora-ketama/tests/old_version/mod.rs index 347dfe43..b6f8dc7f 100644 --- a/pingora-ketama/tests/old_version/mod.rs +++ b/pingora-ketama/tests/old_version/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-limits/Cargo.toml b/pingora-limits/Cargo.toml index c019d636..163b0b12 100644 --- a/pingora-limits/Cargo.toml +++ b/pingora-limits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-limits" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" description = "A library for rate limiting and event frequency estimation" diff --git a/pingora-limits/benches/benchmark.rs b/pingora-limits/benches/benchmark.rs index 699df3dc..4eaa881a 100644 --- a/pingora-limits/benches/benchmark.rs +++ b/pingora-limits/benches/benchmark.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-limits/src/estimator.rs b/pingora-limits/src/estimator.rs index 6f6576d4..bbf91022 100644 --- a/pingora-limits/src/estimator.rs +++ b/pingora-limits/src/estimator.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-limits/src/inflight.rs b/pingora-limits/src/inflight.rs index 9371a12f..c6a25a69 100644 --- a/pingora-limits/src/inflight.rs +++ b/pingora-limits/src/inflight.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-limits/src/lib.rs b/pingora-limits/src/lib.rs index 68492045..c020302b 100644 --- a/pingora-limits/src/lib.rs +++ b/pingora-limits/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-limits/src/rate.rs b/pingora-limits/src/rate.rs index 30977bac..bd1268b3 100644 --- a/pingora-limits/src/rate.rs +++ b/pingora-limits/src/rate.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/Cargo.toml b/pingora-load-balancing/Cargo.toml index 5219aa5e..50dca761 100644 --- a/pingora-load-balancing/Cargo.toml +++ b/pingora-load-balancing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-load-balancing" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -18,11 +18,11 @@ path = "src/lib.rs" [dependencies] async-trait = { workspace = true } -pingora-http = { version = "0.6.0", path = "../pingora-http" } -pingora-error = { version = "0.6.0", path = "../pingora-error" } -pingora-core = { version = "0.6.0", path = "../pingora-core", default-features = false } -pingora-ketama = { version = "0.6.0", path = "../pingora-ketama" } -pingora-runtime = { version = "0.6.0", path = "../pingora-runtime" } +pingora-http = { version = "0.7.0", path = "../pingora-http" } +pingora-error = { version = "0.7.0", path = "../pingora-error" } +pingora-core = { version = "0.7.0", path = "../pingora-core", default-features = false } +pingora-ketama = { version = "0.7.0", path = "../pingora-ketama" } +pingora-runtime = { version = "0.7.0", path = "../pingora-runtime" } arc-swap = "1" fnv = "1" rand = "0.8" @@ -42,4 +42,4 @@ rustls = ["pingora-core/rustls", "any_tls"] s2n = ["pingora-core/s2n", "any_tls"] openssl_derived = ["any_tls"] any_tls = [] -v2 = ["pingora-ketama/v2"] \ No newline at end of file +v2 = ["pingora-ketama/v2"] diff --git a/pingora-load-balancing/src/background.rs b/pingora-load-balancing/src/background.rs index c99c188e..c5b12756 100644 --- a/pingora-load-balancing/src/background.rs +++ b/pingora-load-balancing/src/background.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/src/discovery.rs b/pingora-load-balancing/src/discovery.rs index 2896ec36..afeba278 100644 --- a/pingora-load-balancing/src/discovery.rs +++ b/pingora-load-balancing/src/discovery.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/src/health_check.rs b/pingora-load-balancing/src/health_check.rs index 261126ae..5e97fb36 100644 --- a/pingora-load-balancing/src/health_check.rs +++ b/pingora-load-balancing/src/health_check.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/src/lib.rs b/pingora-load-balancing/src/lib.rs index 7d44929c..2c853037 100644 --- a/pingora-load-balancing/src/lib.rs +++ b/pingora-load-balancing/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/src/selection/algorithms.rs b/pingora-load-balancing/src/selection/algorithms.rs index 4dba4115..cd296c45 100644 --- a/pingora-load-balancing/src/selection/algorithms.rs +++ b/pingora-load-balancing/src/selection/algorithms.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/src/selection/consistent.rs b/pingora-load-balancing/src/selection/consistent.rs index e9ca7194..fe1fe0cb 100644 --- a/pingora-load-balancing/src/selection/consistent.rs +++ b/pingora-load-balancing/src/selection/consistent.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/src/selection/mod.rs b/pingora-load-balancing/src/selection/mod.rs index 029ad6f4..70293a9c 100644 --- a/pingora-load-balancing/src/selection/mod.rs +++ b/pingora-load-balancing/src/selection/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-load-balancing/src/selection/weighted.rs b/pingora-load-balancing/src/selection/weighted.rs index b7b84751..d12c51f6 100644 --- a/pingora-load-balancing/src/selection/weighted.rs +++ b/pingora-load-balancing/src/selection/weighted.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-lru/Cargo.toml b/pingora-lru/Cargo.toml index ffa88bc3..a53e99cd 100644 --- a/pingora-lru/Cargo.toml +++ b/pingora-lru/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-lru" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" diff --git a/pingora-lru/benches/bench_linked_list.rs b/pingora-lru/benches/bench_linked_list.rs index b8a0413f..5fc0e50a 100644 --- a/pingora-lru/benches/bench_linked_list.rs +++ b/pingora-lru/benches/bench_linked_list.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-lru/benches/bench_lru.rs b/pingora-lru/benches/bench_lru.rs index 53acc2e9..c0bdc776 100644 --- a/pingora-lru/benches/bench_lru.rs +++ b/pingora-lru/benches/bench_lru.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-lru/src/lib.rs b/pingora-lru/src/lib.rs index 8ec48f80..23728c4f 100644 --- a/pingora-lru/src/lib.rs +++ b/pingora-lru/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-lru/src/linked_list.rs b/pingora-lru/src/linked_list.rs index 7a9d37cc..ceb9a861 100644 --- a/pingora-lru/src/linked_list.rs +++ b/pingora-lru/src/linked_list.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-memory-cache/Cargo.toml b/pingora-memory-cache/Cargo.toml index bb449610..ef0536cd 100644 --- a/pingora-memory-cache/Cargo.toml +++ b/pingora-memory-cache/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-memory-cache" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -17,14 +17,14 @@ name = "pingora_memory_cache" path = "src/lib.rs" [dependencies] -TinyUFO = { version = "0.6.0", path = "../tinyufo" } +TinyUFO = { version = "0.7.0", path = "../tinyufo" } ahash = { workspace = true } tokio = { workspace = true, features = ["sync"] } async-trait = { workspace = true } -pingora-error = { version = "0.6.0", path = "../pingora-error" } +pingora-error = { version = "0.7.0", path = "../pingora-error" } log = { workspace = true } parking_lot = "0" -pingora-timeout = { version = "0.6.0", path = "../pingora-timeout" } +pingora-timeout = { version = "0.7.0", path = "../pingora-timeout" } [dev-dependencies] once_cell = { workspace = true } diff --git a/pingora-memory-cache/src/lib.rs b/pingora-memory-cache/src/lib.rs index b30a2d2b..84389d0d 100644 --- a/pingora-memory-cache/src/lib.rs +++ b/pingora-memory-cache/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-memory-cache/src/read_through.rs b/pingora-memory-cache/src/read_through.rs index bd9cd3b2..96e4348e 100644 --- a/pingora-memory-cache/src/read_through.rs +++ b/pingora-memory-cache/src/read_through.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-openssl/Cargo.toml b/pingora-openssl/Cargo.toml index 6c472ef7..0bea8477 100644 --- a/pingora-openssl/Cargo.toml +++ b/pingora-openssl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-openssl" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" diff --git a/pingora-openssl/src/ext.rs b/pingora-openssl/src/ext.rs index 25234b95..18e0fdfe 100644 --- a/pingora-openssl/src/ext.rs +++ b/pingora-openssl/src/ext.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-openssl/src/lib.rs b/pingora-openssl/src/lib.rs index 455be746..6fd2f912 100644 --- a/pingora-openssl/src/lib.rs +++ b/pingora-openssl/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-pool/Cargo.toml b/pingora-pool/Cargo.toml index 95b1344f..f7e5798d 100644 --- a/pingora-pool/Cargo.toml +++ b/pingora-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-pool" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -23,7 +23,7 @@ lru = { workspace = true } log = { workspace = true } parking_lot = "0.12" crossbeam-queue = "0.3" -pingora-timeout = { version = "0.6.0", path = "../pingora-timeout" } +pingora-timeout = { version = "0.7.0", path = "../pingora-timeout" } [dev-dependencies] tokio-test = "0.4" diff --git a/pingora-pool/src/connection.rs b/pingora-pool/src/connection.rs index 63f23c46..a30c08ee 100644 --- a/pingora-pool/src/connection.rs +++ b/pingora-pool/src/connection.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -316,7 +316,7 @@ impl ConnectionPool { pub async fn idle_timeout( &self, meta: &ConnectionMeta, - timeout: Duration, + timeout: Option, notify_evicted: Arc, mut notify_closed: watch::Receiver, watch_use: oneshot::Receiver, @@ -335,7 +335,8 @@ impl ConnectionPool { debug!("idle connection is being closed"); self.pop_closed(meta); } - _ = sleep(timeout) => { + // async expression is evaluated if timeout is None but it's never polled, set it to MAX + _ = sleep(timeout.unwrap_or(Duration::MAX)), if timeout.is_some() => { debug!("idle connection is being evicted"); self.pop_closed(meta); } diff --git a/pingora-pool/src/lib.rs b/pingora-pool/src/lib.rs index b3e88692..d16d57b8 100644 --- a/pingora-pool/src/lib.rs +++ b/pingora-pool/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-pool/src/lru.rs b/pingora-pool/src/lru.rs index a7529029..c6a72d8a 100644 --- a/pingora-pool/src/lru.rs +++ b/pingora-pool/src/lru.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/Cargo.toml b/pingora-proxy/Cargo.toml index 9c1265b6..326f66ad 100644 --- a/pingora-proxy/Cargo.toml +++ b/pingora-proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-proxy" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -19,11 +19,11 @@ name = "pingora_proxy" path = "src/lib.rs" [dependencies] -pingora-error = { version = "0.6.0", path = "../pingora-error" } -pingora-core = { version = "0.6.0", path = "../pingora-core", default-features = false } -pingora-cache = { version = "0.6.0", path = "../pingora-cache", default-features = false } +pingora-error = { version = "0.7.0", path = "../pingora-error" } +pingora-core = { version = "0.7.0", path = "../pingora-core", default-features = false } +pingora-cache = { version = "0.7.0", path = "../pingora-cache", default-features = false } tokio = { workspace = true, features = ["macros", "net"] } -pingora-http = { version = "0.6.0", path = "../pingora-http" } +pingora-http = { version = "0.7.0", path = "../pingora-http" } http = { workspace = true } futures = "0.3" bytes = { workspace = true } @@ -44,8 +44,8 @@ tokio-test = "0.4" env_logger = "0.11" hyper = "0.14" tokio-tungstenite = "0.20.1" -pingora-limits = { version = "0.6.0", path = "../pingora-limits" } -pingora-load-balancing = { version = "0.6.0", path = "../pingora-load-balancing", default-features=false } +pingora-limits = { version = "0.7.0", path = "../pingora-limits" } +pingora-load-balancing = { version = "0.7.0", path = "../pingora-load-balancing", default-features=false } prometheus = "0" futures-util = "0.3" serde = { version = "1.0", features = ["derive"] } diff --git a/pingora-proxy/examples/backoff_retry.rs b/pingora-proxy/examples/backoff_retry.rs index d5278f3d..0604b6ec 100644 --- a/pingora-proxy/examples/backoff_retry.rs +++ b/pingora-proxy/examples/backoff_retry.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/connection_filter.rs b/pingora-proxy/examples/connection_filter.rs index 2f103393..1c346c6f 100644 --- a/pingora-proxy/examples/connection_filter.rs +++ b/pingora-proxy/examples/connection_filter.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/ctx.rs b/pingora-proxy/examples/ctx.rs index 106e9e17..bb281a55 100644 --- a/pingora-proxy/examples/ctx.rs +++ b/pingora-proxy/examples/ctx.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/gateway.rs b/pingora-proxy/examples/gateway.rs index dbcae229..83b7c1ca 100644 --- a/pingora-proxy/examples/gateway.rs +++ b/pingora-proxy/examples/gateway.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/grpc_web_module.rs b/pingora-proxy/examples/grpc_web_module.rs index 47969584..085adb92 100644 --- a/pingora-proxy/examples/grpc_web_module.rs +++ b/pingora-proxy/examples/grpc_web_module.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/load_balancer.rs b/pingora-proxy/examples/load_balancer.rs index 17411392..b1375633 100644 --- a/pingora-proxy/examples/load_balancer.rs +++ b/pingora-proxy/examples/load_balancer.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/modify_response.rs b/pingora-proxy/examples/modify_response.rs index 7e498f80..ea10f03f 100644 --- a/pingora-proxy/examples/modify_response.rs +++ b/pingora-proxy/examples/modify_response.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/multi_lb.rs b/pingora-proxy/examples/multi_lb.rs index a0b629c8..c8c76753 100644 --- a/pingora-proxy/examples/multi_lb.rs +++ b/pingora-proxy/examples/multi_lb.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/examples/use_module.rs b/pingora-proxy/examples/use_module.rs index 988e4dde..26c10ca6 100644 --- a/pingora-proxy/examples/use_module.rs +++ b/pingora-proxy/examples/use_module.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/src/lib.rs b/pingora-proxy/src/lib.rs index 206781a1..af99461d 100644 --- a/pingora-proxy/src/lib.rs +++ b/pingora-proxy/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/src/proxy_cache.rs b/pingora-proxy/src/proxy_cache.rs index dbb29ccf..c026d739 100644 --- a/pingora-proxy/src/proxy_cache.rs +++ b/pingora-proxy/src/proxy_cache.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/src/proxy_custom.rs b/pingora-proxy/src/proxy_custom.rs index 50222886..fad19d45 100644 --- a/pingora-proxy/src/proxy_custom.rs +++ b/pingora-proxy/src/proxy_custom.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -125,7 +125,7 @@ where // Custom message logic - let Some(upstream_custom_message_reader) = client_session.take_custom_message_reader() + let Some(mut upstream_custom_message_reader) = client_session.take_custom_message_reader() else { return ( false, @@ -152,7 +152,7 @@ where mpsc::channel(CUSTOM_MESSAGE_QUEUE_SIZE); // Downstream reader - let downstream_custom_message_reader = match session.downstream_custom_message() { + let mut downstream_custom_message_reader = match session.downstream_custom_message() { Ok(Some(rx)) => rx, Ok(None) => Box::new(futures::stream::empty::>()), Err(err) => return (false, Some(err)), @@ -193,7 +193,7 @@ where let upstream_custom_message_forwarder = CustomMessageForwarder { ctx: "down_to_up".into(), - reader: downstream_custom_message_reader, + reader: &mut downstream_custom_message_reader, writer: &mut upstream_custom_message_writer, filter: upstream_custom_message_filter_tx, inject: upstream_custom_message_inject_rx, @@ -202,7 +202,7 @@ where let downstream_custom_message_forwarder = CustomMessageForwarder { ctx: "up_to_down".into(), - reader: upstream_custom_message_reader, + reader: &mut upstream_custom_message_reader, writer: &mut downstream_custom_message_writer, filter: downstream_custom_message_filter_tx, inject: downstream_custom_message_inject_rx, @@ -243,6 +243,10 @@ where custom_session .restore_custom_message_writer(downstream_custom_message_writer) .expect("downstream restore_custom_message_writer should be empty"); + + custom_session + .restore_custom_message_reader(downstream_custom_message_reader) + .expect("downstream restore_custom_message_reader should be empty"); } match ret { @@ -795,7 +799,8 @@ async fn custom_pipe_up_to_down_response( struct CustomMessageForwarder<'a> { ctx: ImmutStr, writer: &'a mut Box, - reader: Box>> + Send + Sync + Unpin>, + reader: + &'a mut Box>> + Send + Sync + Unpin>, inject: mpsc::Receiver, filter: mpsc::Sender<(Bytes, oneshot::Sender>)>, cancel: oneshot::Receiver<()>, diff --git a/pingora-proxy/src/proxy_h1.rs b/pingora-proxy/src/proxy_h1.rs index ca841844..07ec9d05 100644 --- a/pingora-proxy/src/proxy_h1.rs +++ b/pingora-proxy/src/proxy_h1.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -115,12 +115,13 @@ where ); if let Some(custom_session) = session.downstream_session.as_custom_mut() { - match custom_session.restore_custom_message_writer( - downstream_custom_message_writer.expect("downstream be present"), - ) { - Ok(_) => { /* continue */ } - Err(e) => { - return (false, false, Some(e)); + if let Some(downstream_custom_message_writer) = downstream_custom_message_writer { + match custom_session.restore_custom_message_writer(downstream_custom_message_writer) + { + Ok(_) => { /* continue */ } + Err(e) => { + return (false, false, Some(e)); + } } } } @@ -545,6 +546,14 @@ where } } + if let Some(custom_session) = session.downstream_session.as_custom_mut() { + if let Some(downstream_custom_message_reader) = downstream_custom_message_reader { + custom_session + .restore_custom_message_reader(downstream_custom_message_reader) + .expect("downstream restore_custom_message_reader should be empty"); + } + } + let mut reuse_downstream = !downstream_state.is_errored(); if reuse_downstream { match session.as_mut().finish_body().await { diff --git a/pingora-proxy/src/proxy_h2.rs b/pingora-proxy/src/proxy_h2.rs index b8cb156a..8c31ba9c 100644 --- a/pingora-proxy/src/proxy_h2.rs +++ b/pingora-proxy/src/proxy_h2.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -199,12 +199,13 @@ where ); if let Some(custom_session) = session.downstream_session.as_custom_mut() { - match custom_session.restore_custom_message_writer( - downstream_custom_message_writer.expect("downstream be present"), - ) { - Ok(_) => { /* continue */ } - Err(e) => { - return (false, Some(e)); + if let Some(downstream_custom_message_writer) = downstream_custom_message_writer { + match custom_session.restore_custom_message_writer(downstream_custom_message_writer) + { + Ok(_) => { /* continue */ } + Err(e) => { + return (false, Some(e)); + } } } } @@ -500,6 +501,14 @@ where } } + if let Some(custom_session) = session.downstream_session.as_custom_mut() { + if let Some(downstream_custom_message_reader) = downstream_custom_message_reader { + custom_session + .restore_custom_message_reader(downstream_custom_message_reader) + .expect("downstream restore_custom_message_reader should be empty"); + } + } + let mut reuse_downstream = !downstream_state.is_errored(); if reuse_downstream { match session.as_mut().finish_body().await { diff --git a/pingora-proxy/src/proxy_purge.rs b/pingora-proxy/src/proxy_purge.rs index 1f8ead04..cfdb9078 100644 --- a/pingora-proxy/src/proxy_purge.rs +++ b/pingora-proxy/src/proxy_purge.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/src/proxy_trait.rs b/pingora-proxy/src/proxy_trait.rs index 7276a13b..c776c0a3 100644 --- a/pingora-proxy/src/proxy_trait.rs +++ b/pingora-proxy/src/proxy_trait.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/src/subrequest/mod.rs b/pingora-proxy/src/subrequest/mod.rs index 0b61dd33..26c38bff 100644 --- a/pingora-proxy/src/subrequest/mod.rs +++ b/pingora-proxy/src/subrequest/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/tests/test_basic.rs b/pingora-proxy/tests/test_basic.rs index 7b093dfe..c07bbe65 100644 --- a/pingora-proxy/tests/test_basic.rs +++ b/pingora-proxy/tests/test_basic.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/tests/test_upstream.rs b/pingora-proxy/tests/test_upstream.rs index e8a2888a..5125d30a 100644 --- a/pingora-proxy/tests/test_upstream.rs +++ b/pingora-proxy/tests/test_upstream.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -191,7 +191,7 @@ async fn test_download_timeout() { use tokio::time::sleep; let client = hyper::Client::new(); - let uri: hyper::Uri = "http://127.0.0.1:6147/download/".parse().unwrap(); + let uri: hyper::Uri = "http://127.0.0.1:6147/download_large/".parse().unwrap(); let req = hyper::Request::builder() .uri(uri) .header("x-write-timeout", "1") diff --git a/pingora-proxy/tests/utils/cert.rs b/pingora-proxy/tests/utils/cert.rs index 7594afa4..5428f71b 100644 --- a/pingora-proxy/tests/utils/cert.rs +++ b/pingora-proxy/tests/utils/cert.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf b/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf index 84211ae3..97bd666b 100644 --- a/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf +++ b/pingora-proxy/tests/utils/conf/origin/conf/nginx.conf @@ -99,7 +99,6 @@ http { # increase max body size for /upload/ test client_max_body_size 128m; - #charset koi8-r; #access_log logs/host.access.log main; @@ -319,6 +318,19 @@ http { } } + location /download_large/ { + content_by_lua_block { + ngx.req.read_body() + local chunk = string.rep("A", 1048576) -- 1MB chunk + local total_size = 128 * 1048576 -- 128MB total + ngx.header["Content-Length"] = total_size + for i = 1, 128 do + ngx.print(chunk) + ngx.flush() + end + } + } + location /tls_verify { keepalive_timeout 0; return 200; diff --git a/pingora-proxy/tests/utils/mock_origin.rs b/pingora-proxy/tests/utils/mock_origin.rs index f3564dbe..74840e19 100644 --- a/pingora-proxy/tests/utils/mock_origin.rs +++ b/pingora-proxy/tests/utils/mock_origin.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/tests/utils/mod.rs b/pingora-proxy/tests/utils/mod.rs index 7a70ae4f..3ec2fa28 100644 --- a/pingora-proxy/tests/utils/mod.rs +++ b/pingora-proxy/tests/utils/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-proxy/tests/utils/server_utils.rs b/pingora-proxy/tests/utils/server_utils.rs index e571482e..7a433acf 100644 --- a/pingora-proxy/tests/utils/server_utils.rs +++ b/pingora-proxy/tests/utils/server_utils.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-runtime/Cargo.toml b/pingora-runtime/Cargo.toml index de419400..b5fd4372 100644 --- a/pingora-runtime/Cargo.toml +++ b/pingora-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-runtime" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" diff --git a/pingora-runtime/benches/hello.rs b/pingora-runtime/benches/hello.rs index 3460efb1..271447e5 100644 --- a/pingora-runtime/benches/hello.rs +++ b/pingora-runtime/benches/hello.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-runtime/src/lib.rs b/pingora-runtime/src/lib.rs index 07883400..a0468f4f 100644 --- a/pingora-runtime/src/lib.rs +++ b/pingora-runtime/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-rustls/Cargo.toml b/pingora-rustls/Cargo.toml index f2540349..ec3f378c 100644 --- a/pingora-rustls/Cargo.toml +++ b/pingora-rustls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-rustls" -version = "0.6.0" +version = "0.7.0" license = "Apache-2.0" edition = "2021" repository = "https://github.com/cloudflare/pingora" @@ -16,7 +16,7 @@ path = "src/lib.rs" [dependencies] log = "0.4.21" -pingora-error = { version = "0.6.0", path = "../pingora-error"} +pingora-error = { version = "0.7.0", path = "../pingora-error"} ring = "0.17.12" rustls = "0.23.12" rustls-native-certs = "0.7.1" diff --git a/pingora-rustls/src/lib.rs b/pingora-rustls/src/lib.rs index 2e88c94d..a06012be 100644 --- a/pingora-rustls/src/lib.rs +++ b/pingora-rustls/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ use pingora_error::{Error, ErrorType, OrErr, Result}; pub use rustls::{ client::WebPkiServerVerifier, version, CertificateError, ClientConfig, DigitallySignedStruct, - Error as RusTlsError, RootCertStore, ServerConfig, SignatureScheme, Stream, + Error as RusTlsError, KeyLogFile, RootCertStore, ServerConfig, SignatureScheme, Stream, }; pub use rustls_native_certs::load_native_certs; use rustls_pemfile::Item; diff --git a/pingora-s2n/Cargo.toml b/pingora-s2n/Cargo.toml index 9ecf1087..22b1b308 100644 --- a/pingora-s2n/Cargo.toml +++ b/pingora-s2n/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-s2n" -version = "0.6.0" +version = "0.7.0" license = "Apache-2.0" edition = "2021" repository = "https://github.com/cloudflare/pingora" @@ -15,7 +15,7 @@ name = "pingora_s2n" path = "src/lib.rs" [dependencies] -pingora-error = {version = "0.6.0", path = "../pingora-error"} +pingora-error = { version = "0.7.0", path = "../pingora-error"} ring = "0.17.12" s2n-tls = "0.3" s2n-tls-tokio = "0.3" diff --git a/pingora-s2n/src/lib.rs b/pingora-s2n/src/lib.rs index 2a7a476e..aef1cef3 100644 --- a/pingora-s2n/src/lib.rs +++ b/pingora-s2n/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-timeout/Cargo.toml b/pingora-timeout/Cargo.toml index ff14283c..f2d8b5a1 100644 --- a/pingora-timeout/Cargo.toml +++ b/pingora-timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora-timeout" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" diff --git a/pingora-timeout/benches/benchmark.rs b/pingora-timeout/benches/benchmark.rs index ae32556c..64fd053d 100644 --- a/pingora-timeout/benches/benchmark.rs +++ b/pingora-timeout/benches/benchmark.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-timeout/src/fast_timeout.rs b/pingora-timeout/src/fast_timeout.rs index 8fd22908..27535e11 100644 --- a/pingora-timeout/src/fast_timeout.rs +++ b/pingora-timeout/src/fast_timeout.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-timeout/src/lib.rs b/pingora-timeout/src/lib.rs index c0498c3e..707f7be8 100644 --- a/pingora-timeout/src/lib.rs +++ b/pingora-timeout/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora-timeout/src/timer.rs b/pingora-timeout/src/timer.rs index e0f631a7..c6c587e0 100644 --- a/pingora-timeout/src/timer.rs +++ b/pingora-timeout/src/timer.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/Cargo.toml b/pingora/Cargo.toml index 2834c8e7..5380c27b 100644 --- a/pingora/Cargo.toml +++ b/pingora/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pingora" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] license = "Apache-2.0" edition = "2021" @@ -22,12 +22,12 @@ features = ["document-features"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -pingora-core = { version = "0.6.0", path = "../pingora-core", default-features = false } -pingora-http = { version = "0.6.0", path = "../pingora-http" } -pingora-timeout = { version = "0.6.0", path = "../pingora-timeout" } -pingora-load-balancing = { version = "0.6.0", path = "../pingora-load-balancing", optional = true, default-features = false } -pingora-proxy = { version = "0.6.0", path = "../pingora-proxy", optional = true, default-features = false } -pingora-cache = { version = "0.6.0", path = "../pingora-cache", optional = true, default-features = false } +pingora-core = { version = "0.7.0", path = "../pingora-core", default-features = false } +pingora-http = { version = "0.7.0", path = "../pingora-http" } +pingora-timeout = { version = "0.7.0", path = "../pingora-timeout" } +pingora-load-balancing = { version = "0.7.0", path = "../pingora-load-balancing", optional = true, default-features = false } +pingora-proxy = { version = "0.7.0", path = "../pingora-proxy", optional = true, default-features = false } +pingora-cache = { version = "0.7.0", path = "../pingora-cache", optional = true, default-features = false } # Only used for documenting features, but doesn't work in any other dependency # group :( diff --git a/pingora/examples/app/echo.rs b/pingora/examples/app/echo.rs index 97e449df..fd1daeb4 100644 --- a/pingora/examples/app/echo.rs +++ b/pingora/examples/app/echo.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/examples/app/mod.rs b/pingora/examples/app/mod.rs index a9fa06e8..1f6c3e61 100644 --- a/pingora/examples/app/mod.rs +++ b/pingora/examples/app/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/examples/app/proxy.rs b/pingora/examples/app/proxy.rs index 042b5112..4760957a 100644 --- a/pingora/examples/app/proxy.rs +++ b/pingora/examples/app/proxy.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/examples/client.rs b/pingora/examples/client.rs index 44efaa2d..30be7b2f 100644 --- a/pingora/examples/client.rs +++ b/pingora/examples/client.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/examples/server.rs b/pingora/examples/server.rs index fffcb1cc..9c6f8452 100644 --- a/pingora/examples/server.rs +++ b/pingora/examples/server.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/examples/service/echo.rs b/pingora/examples/service/echo.rs index 83b46ed4..a2e0f32e 100644 --- a/pingora/examples/service/echo.rs +++ b/pingora/examples/service/echo.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/examples/service/mod.rs b/pingora/examples/service/mod.rs index a9fa06e8..1f6c3e61 100644 --- a/pingora/examples/service/mod.rs +++ b/pingora/examples/service/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/examples/service/proxy.rs b/pingora/examples/service/proxy.rs index 39de498d..1c6a1df9 100644 --- a/pingora/examples/service/proxy.rs +++ b/pingora/examples/service/proxy.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pingora/src/lib.rs b/pingora/src/lib.rs index a102050e..e72cb28c 100644 --- a/pingora/src/lib.rs +++ b/pingora/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tinyufo/Cargo.toml b/tinyufo/Cargo.toml index 08a4c18b..16d5e497 100644 --- a/tinyufo/Cargo.toml +++ b/tinyufo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "TinyUFO" -version = "0.6.0" +version = "0.7.0" authors = ["Yuchen Wu "] edition = "2021" license = "Apache-2.0" diff --git a/tinyufo/benches/bench_hit_ratio.rs b/tinyufo/benches/bench_hit_ratio.rs index dcd666c5..4c162fbe 100644 --- a/tinyufo/benches/bench_hit_ratio.rs +++ b/tinyufo/benches/bench_hit_ratio.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tinyufo/benches/bench_memory.rs b/tinyufo/benches/bench_memory.rs index cb8f3605..2f770027 100644 --- a/tinyufo/benches/bench_memory.rs +++ b/tinyufo/benches/bench_memory.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tinyufo/benches/bench_perf.rs b/tinyufo/benches/bench_perf.rs index 5d05b8b9..cb0638d6 100644 --- a/tinyufo/benches/bench_perf.rs +++ b/tinyufo/benches/bench_perf.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tinyufo/src/buckets.rs b/tinyufo/src/buckets.rs index 644b3375..d74ab6bf 100644 --- a/tinyufo/src/buckets.rs +++ b/tinyufo/src/buckets.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tinyufo/src/estimation.rs b/tinyufo/src/estimation.rs index bd6c764a..8e187931 100644 --- a/tinyufo/src/estimation.rs +++ b/tinyufo/src/estimation.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/tinyufo/src/lib.rs b/tinyufo/src/lib.rs index a8509e21..4064a356 100644 --- a/tinyufo/src/lib.rs +++ b/tinyufo/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Cloudflare, Inc. +// Copyright 2026 Cloudflare, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.