v0.34.0_1

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-17 20:34:13 +02:00
1 parent 9b3d6731cc
commit 00f29f92c8
68 files changed
+2599 -513

No files matched your search

+2 -2
View File
@@ -207,8 +207,8 @@ fn compile_local_hosts(config: &Config) -> Result<LocalHostBuild> {
}
struct RoutingBuild {
routes: HashMap<String, Vec<CompiledRoute>>,
fallbacks: HashMap<String, Vec<CompiledRoute>>,
routes: HashMap<String, Vec<Arc<CompiledRoute>>>,
fallbacks: HashMap<String, Vec<Arc<CompiledRoute>>>,
frontends: HashMap<String, Vec<Arc<CompiledFrontend>>>,
redirects: HashMap<String, Vec<Arc<crate::redirect::CompiledHost>>>,
not_found_hosts: HashMap<String, Vec<Arc<crate::not_found::CompiledHost>>>,
+14 -2
View File
@@ -10,8 +10,20 @@ impl Snapshot {
local: SocketAddr,
tls: bool,
) -> Option<&CompiledRoute> {
self.select_internal_on_arc(host, vhost_id, target, local, tls)
.map(Arc::as_ref)
}
pub fn select_internal_on_arc(
&self,
host: &str,
vhost_id: &str,
target: &str,
local: SocketAddr,
tls: bool,
) -> Option<&Arc<CompiledRoute>> {
for pattern in Self::host_patterns(host) {
let owns_host = self.fallbacks.get(&pattern).is_some_and(|routes| {
let owns_host = self.fallbacks.get(pattern.as_ref()).is_some_and(|routes| {
routes.iter().any(|route| {
route.frontend.as_ref().is_some_and(|frontend| frontend.id == vhost_id)
&& route.bindings.iter().any(|binding| binding.matches(local, tls))
@@ -20,7 +32,7 @@ impl Snapshot {
if !owns_host {
continue;
}
return self.routes.get(&pattern).and_then(|routes| {
return self.routes.get(pattern.as_ref()).and_then(|routes| {
routes.iter().find(|route| {
route.internal
&& route.name == target
+2 -2
View File
@@ -289,9 +289,9 @@ pub struct Snapshot {
frontends: HashMap<String, Vec<Arc<CompiledFrontend>>>,
pub tls: Option<Arc<rustls::ServerConfig>>,
pub envelope: Envelope,
routes: HashMap<String, Vec<CompiledRoute>>,
routes: HashMap<String, Vec<Arc<CompiledRoute>>>,
/// Main vhost backend fallback, kept separate from user Custom locations.
fallbacks: HashMap<String, Vec<CompiledRoute>>,
fallbacks: HashMap<String, Vec<Arc<CompiledRoute>>>,
redirects: HashMap<String, Vec<Arc<crate::redirect::CompiledHost>>>,
not_found_hosts: HashMap<String, Vec<Arc<crate::not_found::CompiledHost>>>,
host_acls: HashMap<String, Arc<CompiledAcl>>,
+6 -6
View File
@@ -9,8 +9,8 @@ fn compile_routing(
effective_tls: &crate::tls::Settings,
tls: &Option<Arc<rustls::ServerConfig>>,
) -> Result<RoutingBuild> {
let mut routes: HashMap<String, Vec<CompiledRoute>> = HashMap::new();
let mut fallbacks: HashMap<String, Vec<CompiledRoute>> = HashMap::new();
let mut routes: HashMap<String, Vec<Arc<CompiledRoute>>> = HashMap::new();
let mut fallbacks: HashMap<String, Vec<Arc<CompiledRoute>>> = HashMap::new();
let mut frontends: HashMap<String, Vec<Arc<CompiledFrontend>>> = HashMap::new();
let (redirects, not_found_hosts, mut claimed_hosts) = compile_local_hosts(config)?;
let mut pools = Vec::new();
@@ -247,7 +247,7 @@ fn compile_routing(
None,
),
};
let fallback = CompiledRoute {
let fallback = Arc::new(CompiledRoute {
backend_id: None,
bindings: frontend.bindings.clone(),
frontend: Some(frontend.clone()),
@@ -273,7 +273,7 @@ fn compile_routing(
method_policy: crate::vhost::MethodPolicy::default(),
headers: crate::headers::CompiledHeaders::default(),
unavailable: host.transient_unavailable && host.source.is_reverse_proxy(),
};
});
for name in &routing_hosts {
fallbacks
.entry(name.clone())
@@ -389,7 +389,7 @@ fn compile_routing(
),
}
};
let compiled = CompiledRoute {
let compiled = Arc::new(CompiledRoute {
backend_id: route.backend_id.clone(),
bindings: frontend.bindings.clone(),
frontend: Some(frontend.clone()),
@@ -415,7 +415,7 @@ fn compile_routing(
method_policy: route.method_policy.clone(),
headers,
unavailable: false,
};
});
for name in &routing_hosts {
routes
.entry(name.clone())
+31 -21
View File
@@ -1,17 +1,16 @@
impl Snapshot {
fn host_patterns(host: &str) -> Vec<String> {
let mut patterns = vec![host.to_owned()];
for (index, _) in host.match_indices('.') {
let suffix = &host[index + 1..];
if suffix.contains('.') {
patterns.push(format!("*.{suffix}"));
}
}
patterns
// Exact host: no allocation. Wildcards: lazy and most-specific-first.
fn host_patterns(host: &str) -> impl Iterator<Item = std::borrow::Cow<'_, str>> {
std::iter::once(std::borrow::Cow::Borrowed(host)).chain(
host.match_indices('.').filter_map(move |(index, _)| {
let suffix = &host[index + 1..];
suffix.contains('.').then(|| std::borrow::Cow::Owned(format!("*.{suffix}")))
}),
)
}
pub fn host_allowed(&self, host: &str, peer: IpAddr) -> bool {
for pattern in Self::host_patterns(host) {
if let Some(acl) = self.host_acls.get(&pattern) {
if let Some(acl) = self.host_acls.get(pattern.as_ref()) {
return acl.allows(peer);
}
}
@@ -53,8 +52,19 @@ impl Snapshot {
local: SocketAddr,
tls: bool,
) -> Option<&CompiledRoute> {
self.select_on_arc(host, path, local, tls).map(Arc::as_ref)
}
/// Shared compiled-route handle for middleware and forward().
pub fn select_on_arc(
&self,
host: &str,
path: &str,
local: SocketAddr,
tls: bool,
) -> Option<&Arc<CompiledRoute>> {
for pattern in Self::host_patterns(host) {
let fallback = self.fallbacks.get(&pattern).and_then(|routes| {
let fallback = self.fallbacks.get(pattern.as_ref()).and_then(|routes| {
routes
.iter()
.find(|route| route.bindings.iter().any(|l| l.matches(local, tls)))
@@ -65,7 +75,7 @@ impl Snapshot {
if let Some(fallback) = fallback {
return self
.routes
.get(&pattern)
.get(pattern.as_ref())
.and_then(|routes| {
select_route(routes, path, |r| {
r.bindings.iter().any(|l| l.matches(local, tls))
@@ -76,7 +86,6 @@ impl Snapshot {
}
None
}
pub fn frontend(
&self,
host: &str,
@@ -84,7 +93,7 @@ impl Snapshot {
tls: bool,
) -> Option<&Arc<CompiledFrontend>> {
for pattern in Self::host_patterns(host) {
if let Some(frontend) = self.frontends.get(&pattern).and_then(|frontends| {
if let Some(frontend) = self.frontends.get(pattern.as_ref()).and_then(|frontends| {
frontends
.iter()
.find(|f| f.bindings.iter().any(|l| l.matches(local, tls)))
@@ -155,7 +164,7 @@ impl Snapshot {
&crate::redirect::CompiledRule,
)> {
for pattern in Self::host_patterns(host) {
if let Some((redirect, rule)) = self.redirects.get(&pattern).and_then(|redirects| {
if let Some((redirect, rule)) = self.redirects.get(pattern.as_ref()).and_then(|redirects| {
redirects.iter().find_map(|redirect| {
if !redirect
.bindings
@@ -184,7 +193,7 @@ impl Snapshot {
tls: bool,
) -> Option<&Arc<crate::not_found::CompiledHost>> {
for pattern in Self::host_patterns(host) {
if let Some(item) = self.not_found_hosts.get(&pattern).and_then(|items| {
if let Some(item) = self.not_found_hosts.get(pattern.as_ref()).and_then(|items| {
items.iter().find(|item| {
item.bindings
.iter()
@@ -202,14 +211,15 @@ impl Snapshot {
for pattern in Self::host_patterns(host) {
if let Some(fallback) = self
.fallbacks
.get(&pattern)
.get(pattern.as_ref())
.and_then(|routes| routes.first())
{
return self
.routes
.get(&pattern)
.get(pattern.as_ref())
.and_then(|routes| select_route(routes, path, |_| true))
.or(Some(fallback));
.or(Some(fallback))
.map(Arc::as_ref);
}
}
None
@@ -217,10 +227,10 @@ impl Snapshot {
}
fn select_route<'a, F>(
routes: &'a [CompiledRoute],
routes: &'a [Arc<CompiledRoute>],
path: &str,
allowed: F,
) -> Option<&'a CompiledRoute>
) -> Option<&'a Arc<CompiledRoute>>
where
F: Fn(&CompiledRoute) -> bool,
{
+13
View File
@@ -9,6 +9,19 @@ fn policy() -> ValidationPolicy {
admin_addr: "127.0.0.1:9090".parse().unwrap(),
}
}
#[test]
fn route_handles_reuse_snapshot_ownership_without_cloning_compiled_routes() {
let snapshot = compile(demo_config("127.0.0.1:9000".parse().unwrap()), 1, &policy()).unwrap();
let local = "127.0.0.1:8080".parse().unwrap();
let first = snapshot.select_on_arc("localhost", "/", local, false).unwrap();
let second = snapshot.select_on_arc("localhost", "/child", local, false).unwrap();
// Both selectors point into one immutable compiled snapshot.
assert!(std::sync::Arc::ptr_eq(first, second));
let legacy = snapshot.select_on("localhost", "/", local, false).unwrap();
assert!(std::ptr::eq(first.as_ref(), legacy));
assert!(snapshot.select_on_arc("not-this-host", "/", local, false).is_none());
}
#[test]
fn missing_http2_settings_use_release_defaults() {
let config: Config = serde_json::from_value(serde_json::json!({})).unwrap();