1pub mod parser;
29pub mod health;
30pub(crate) mod upstream;
31
32pub use upstream::{BreakerConfig, RetryConfig, StickyConfig};
33pub mod builder;
34pub mod validate;
35pub mod mcp_gateway;
36pub mod a2a_gateway;
37pub mod llm;
38
39#[cfg(test)]
40mod tests;
41
42use std::sync::atomic::{AtomicBool, Ordering};
43use std::sync::{Arc, RwLock};
44
45use crate::app::App;
46use crate::application::Application;
47use crate::core::New;
48use crate::request::Request;
49use crate::header::Header;
50use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
51use crate::router::{host_matches, request_host};
52use crate::server::ConnectionInfo;
53use crate::server_config::ServerConfig;
54
55#[derive(Debug, Clone)]
58pub struct ProxyConfig {
59 pub upstreams: Vec<UpstreamConfig>,
60 pub routes: Vec<RouteConfig>,
61 pub tcp_proxies: Vec<TcpProxyConfig>,
62 pub udp_proxies: Vec<UdpProxyConfig>,
63 pub ws_proxies: Vec<WsProxyConfig>,
64 pub global_middleware: MiddlewareConfig,
65 pub authz: Option<AuthzConfig>,
67}
68
69#[derive(Debug, Clone, Default)]
71pub struct AuthzConfig {
72 pub store: String,
74 pub tuples: Vec<String>,
76 pub types: Vec<(String, Vec<(String, String)>)>,
78}
79
80#[derive(Debug, Clone)]
82pub struct RouteAuthzConfig {
83 pub relation: String,
84 pub object: String,
86}
87
88#[derive(Debug, Clone)]
89pub struct UpstreamConfig {
90 pub name: String,
91 pub backends: Vec<String>,
92 pub strategy: String, pub health_check: Option<HealthCheckConfig>,
94 pub tls: bool,
98 pub tls_ca_file: Option<String>,
102 pub tls_client_cert_file: Option<String>,
106 pub tls_client_key_file: Option<String>,
107 pub weights: Vec<u32>,
109 pub sticky: Option<StickyConfig>,
111 pub circuit_breaker: Option<BreakerConfig>,
114 pub retry: Option<RetryConfig>,
116 pub protocol: String,
121}
122
123#[derive(Debug, Clone)]
124pub struct HealthCheckConfig {
125 pub path: String,
126 pub interval_secs: u64,
127 pub timeout_ms: u64,
128 pub healthy_threshold: u32,
129 pub unhealthy_threshold: u32,
130}
131
132#[derive(Debug, Clone)]
133pub struct RouteConfig {
134 pub name: String,
135 pub match_: MatchConfig,
136 pub action: ActionConfig,
137 pub middleware: MiddlewareConfig,
138}
139
140#[derive(Debug, Clone, Default)]
141pub struct MatchConfig {
142 pub host: Option<String>,
143 pub path: Option<String>,
144 pub method: Option<String>,
145 pub content_type: Option<String>,
146}
147
148#[derive(Debug, Clone, Default, PartialEq, Eq)]
150pub enum UpstreamHost {
151 #[default]
153 Backend,
154 Client,
157 Fixed(String),
159}
160
161#[derive(Debug, Clone)]
162pub enum ActionConfig {
163 Proxy {
164 upstream: String,
165 connect_timeout_ms: u64,
166 read_timeout_ms: u64,
167 strip_path_prefix: Option<String>,
168 add_path_prefix: Option<String>,
169 upstream_host: UpstreamHost,
171 },
172 Grpc {
173 upstream: String,
174 connect_timeout_ms: u64,
175 read_timeout_ms: u64,
176 },
177 Static {
178 root: String,
179 index: Vec<String>,
180 },
181 Redirect {
182 location: String,
183 status: u16,
184 },
185 Respond {
186 status: u16,
187 body: String,
188 content_type: String,
189 },
190 Mcp(Box<mcp_gateway::McpGatewayConfig>),
192 A2a(Box<a2a_gateway::A2aGatewayConfig>),
194 Llm(Box<llm::LlmConfig>),
196 Unknown(String),
197}
198
199#[derive(Debug, Clone, Default)]
200pub struct MiddlewareConfig {
201 pub rate_limit: Option<RateLimitConfig>,
202 pub cache: Option<CacheConfig>,
203 pub auth: Option<AuthConfig>,
204 pub rewrite_request: Vec<RewriteRuleConfig>,
205 pub rewrite_response: Vec<RewriteRuleConfig>,
206 pub ip_allow: Vec<String>,
207 pub ip_deny: Vec<String>,
208 pub timeout_ms: Option<u64>,
213 pub max_body_size: Option<u64>,
229 pub wasm: Option<WasmConfig>,
232 pub early_hints: Vec<String>,
235 pub authz: Option<RouteAuthzConfig>,
238 pub mirror: Option<MirrorConfig>,
241}
242
243#[derive(Debug, Clone, Default, PartialEq)]
245pub struct MirrorConfig {
246 pub upstream: String,
248 pub backends: Vec<String>,
250 pub percent: f64,
252}
253
254#[derive(Debug, Clone, Default, PartialEq)]
256pub struct WasmConfig {
257 pub module: String,
259 pub fuel: u64,
260 pub memory_mb: u64,
261 pub config: String,
263}
264
265#[derive(Debug, Clone)]
266pub struct RateLimitConfig {
267 pub max_requests: u32,
268 pub window_secs: u64,
269}
270
271#[derive(Debug, Clone, Default)]
272pub struct CacheConfig {
273 pub ttl_secs: u64,
274 pub vary_by: Vec<String>,
275 pub dir: String,
277 pub max_size: String,
279 pub purge_token_env: String,
282}
283
284#[derive(Debug, Clone)]
285pub enum AuthConfig {
286 Basic { htpasswd_file: String },
291 Jwt { secret_env: String, previous_secret_env: String },
296 Bearer { token_env: String },
297}
298
299#[derive(Debug, Clone, Default)]
300pub struct RewriteRuleConfig {
301 pub type_: String,
302 pub name: Option<String>,
303 pub value: Option<String>,
304 pub prefix: Option<String>,
305 pub from: Option<String>,
306 pub to: Option<String>,
307 pub code: Option<u16>,
308 pub reason: Option<String>,
309}
310
311#[derive(Debug, Clone)]
312pub struct TcpProxyConfig {
313 pub name: String,
314 pub listen: String,
315 pub backends: Vec<String>,
316 pub connect_timeout_ms: u64,
317}
318
319#[derive(Debug, Clone)]
320pub struct UdpProxyConfig {
321 pub name: String,
322 pub listen: String,
323 pub backends: Vec<String>,
324 pub reply_timeout_ms: u64,
325 pub buffer_size: usize,
326}
327
328#[derive(Debug, Clone)]
329pub struct WsProxyConfig {
330 pub name: String,
331 pub listen: String,
332 pub backends: Vec<String>,
333 pub connect_timeout_ms: u64,
334 pub read_timeout_ms: u64,
335 pub health_check: Option<HealthCheckConfig>,
342}
343
344fn merge_action_keys(map: &mut parser::SectionMap, action_section: &str, action_type: &str) {
353 if action_type.is_empty() {
354 return;
355 }
356 let short_form: Vec<(String, String)> = match map.get(action_section) {
357 Some(entries) => entries.iter().filter(|(k, _)| k != "type").cloned().collect(),
358 None => return,
359 };
360 let nested = map.entry(format!("{action_section}.{action_type}")).or_default();
361 for (key, value) in short_form {
362 if !nested.iter().any(|(k, _)| *k == key) {
363 nested.push((key, value));
364 }
365 }
366}
367
368impl ProxyConfig {
369 pub fn is_proxy_mode() -> bool {
373 let path = config_file_path();
374 match std::fs::read_to_string(&path) {
375 Ok(contents) => {
376 contents.contains("[[route]]") || contents.contains("[[upstream]]")
377 }
378 Err(_) => false,
379 }
380 }
381
382 pub fn load() -> Self {
384 let path = config_file_path();
385 let contents = std::fs::read_to_string(&path).unwrap_or_default();
386 Self::from_str(&contents)
387 }
388
389 pub fn from_str(toml: &str) -> Self {
391 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
392
393 let mut map = parser::parse(toml);
394
395 let mut upstreams = Vec::new();
397 let mut i = 0;
398 loop {
399 let sec = format!("upstream[{}]", i);
400 if !section_exists(&map, &sec) {
401 break;
402 }
403 let name = get_str(&map, &sec, "name");
404 let backends = get_array(&map, &sec, "backends");
405 let strategy = {
406 let s = get_str(&map, &sec, "strategy");
407 if s.is_empty() { "round_robin".to_string() } else { s }
408 };
409 let hc_sec = format!("{}.health_check", sec);
410 let health_check = if section_exists(&map, &hc_sec) {
411 Some(HealthCheckConfig {
412 path: {
413 let p = get_str(&map, &hc_sec, "path");
414 if p.is_empty() { "/health".to_string() } else { p }
415 },
416 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
417 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
418 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
419 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
420 })
421 } else {
422 None
423 };
424 let tls = backends.iter().any(|b| ["https://", "h2s://", "grpcs://"].iter().any(|s| b.starts_with(s)));
425 let tls_ca_file = Some(get_str(&map, &sec, "tls_ca_file")).filter(|v| !v.is_empty());
426 let tls_client_cert_file = Some(get_str(&map, &sec, "tls_client_cert_file")).filter(|v| !v.is_empty());
427 let tls_client_key_file = Some(get_str(&map, &sec, "tls_client_key_file")).filter(|v| !v.is_empty());
428 let weights: Vec<u32> = get_array(&map, &sec, "weights").iter().map(|w| w.trim().parse().unwrap_or(0)).collect();
429 let sticky_sec = format!("{}.sticky", sec);
430 let sticky = section_exists(&map, &sticky_sec).then(|| StickyConfig {
431 cookie: Some(get_str(&map, &sticky_sec, "cookie")).filter(|c| !c.is_empty()).unwrap_or_else(|| "rws_backend".to_string()),
432 max_age_secs: Some(get_u64(&map, &sticky_sec, "max_age_secs", 0)).filter(|s| *s > 0),
433 });
434 let cb_sec = format!("{}.circuit_breaker", sec);
435 let circuit_breaker = section_exists(&map, &cb_sec).then(|| BreakerConfig {
436 failure_threshold: get_u32(&map, &cb_sec, "failure_threshold", 5),
437 recovery_secs: get_u64(&map, &cb_sec, "recovery_secs", 30),
438 half_open_probes: get_u32(&map, &cb_sec, "half_open_probes", 1),
439 });
440 let retry_sec = format!("{}.retry", sec);
441 let retry = section_exists(&map, &retry_sec).then(|| {
442 let status: Vec<i16> = get_array(&map, &retry_sec, "status").iter().filter_map(|s| s.trim().parse().ok()).collect();
443 RetryConfig { attempts: get_u32(&map, &retry_sec, "attempts", 1), status: if status.is_empty() { vec![502, 503, 504] } else { status } }
444 });
445 upstreams.push(UpstreamConfig {
446 name, backends, strategy, health_check, tls, tls_ca_file, tls_client_cert_file, tls_client_key_file,
447 weights, sticky, circuit_breaker, retry,
448 protocol: get_str(&map, &sec, "protocol"),
449 });
450 i += 1;
451 }
452
453 let mut routes = Vec::new();
455 let mut i = 0;
456 loop {
457 let sec = format!("route[{}]", i);
458 if !section_exists(&map, &sec) {
459 break;
460 }
461 let name = get_str(&map, &sec, "name");
462
463 let m_sec = format!("{}.match", sec);
465 let match_ = MatchConfig {
466 host: {
467 let h = get_str(&map, &m_sec, "host");
468 if h.is_empty() { None } else { Some(h) }
469 },
470 path: {
471 let p = get_str(&map, &m_sec, "path");
472 if p.is_empty() { None } else { Some(p) }
473 },
474 method: {
475 let m = get_str(&map, &m_sec, "method");
476 if m.is_empty() { None } else { Some(m.to_uppercase()) }
477 },
478 content_type: {
479 let c = get_str(&map, &m_sec, "content_type");
480 if c.is_empty() { None } else { Some(c) }
481 },
482 };
483
484 let a_sec = format!("{}.action", sec);
486 let action_type = get_str(&map, &a_sec, "type");
487 merge_action_keys(&mut map, &a_sec, &action_type);
488 let action = match action_type.as_str() {
489 "proxy" => {
490 let p_sec = format!("{}.action.proxy", sec);
491 ActionConfig::Proxy {
492 upstream: get_str(&map, &p_sec, "upstream"),
493 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
494 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
495 strip_path_prefix: {
496 let v = get_str(&map, &p_sec, "strip_path_prefix");
497 if v.is_empty() { None } else { Some(v) }
498 },
499 add_path_prefix: {
500 let v = get_str(&map, &p_sec, "add_path_prefix");
501 if v.is_empty() { None } else { Some(v) }
502 },
503 upstream_host: {
504 let fixed = get_str(&map, &p_sec, "upstream_host");
505 if !fixed.is_empty() {
506 UpstreamHost::Fixed(fixed)
507 } else if get_str(&map, &p_sec, "preserve_host") == "true" {
508 UpstreamHost::Client
509 } else {
510 UpstreamHost::Backend
511 }
512 },
513 }
514 }
515 "grpc" => {
516 let p_sec = format!("{}.action.grpc", sec);
517 ActionConfig::Grpc {
518 upstream: get_str(&map, &p_sec, "upstream"),
519 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
520 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
521 }
522 }
523 "static" => {
524 let s_sec = format!("{}.action.static", sec);
525 ActionConfig::Static {
526 root: get_str(&map, &s_sec, "root"),
527 index: get_array(&map, &s_sec, "index"),
528 }
529 }
530 "redirect" => {
531 let r_sec = format!("{}.action.redirect", sec);
532 ActionConfig::Redirect {
533 location: get_str(&map, &r_sec, "location"),
534 status: get_u64(&map, &r_sec, "status", 301) as u16,
535 }
536 }
537 "respond" => {
538 let r_sec = format!("{}.action.respond", sec);
539 ActionConfig::Respond {
540 status: get_u64(&map, &r_sec, "status", 200) as u16,
541 body: get_str(&map, &r_sec, "body"),
542 content_type: {
543 let ct = get_str(&map, &r_sec, "content_type");
544 if ct.is_empty() { "text/plain".to_string() } else { ct }
545 },
546 }
547 }
548 "mcp" => ActionConfig::Mcp(Box::new(mcp_gateway::parse(&map, &a_sec))),
549 "a2a" => ActionConfig::A2a(Box::new(a2a_gateway::parse(&map, &a_sec))),
550 "llm" => ActionConfig::Llm(Box::new(llm::parse(&map, &a_sec))),
551 other => ActionConfig::Unknown(other.to_string()),
552 };
553
554 let mw_sec = format!("{}.middleware", sec);
556 let middleware = parse_middleware_config(&map, &mw_sec, i);
557
558 routes.push(RouteConfig { name, match_, action, middleware });
559 i += 1;
560 }
561
562 let mut tcp_proxies = Vec::new();
564 let mut i = 0;
565 loop {
566 let sec = format!("tcp_proxy[{}]", i);
567 if !section_exists(&map, &sec) {
568 break;
569 }
570 tcp_proxies.push(TcpProxyConfig {
571 name: get_str(&map, &sec, "name"),
572 listen: get_str(&map, &sec, "listen"),
573 backends: get_array(&map, &sec, "backends"),
574 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
575 });
576 i += 1;
577 }
578
579 let mut udp_proxies = Vec::new();
581 let mut i = 0;
582 loop {
583 let sec = format!("udp_proxy[{}]", i);
584 if !section_exists(&map, &sec) {
585 break;
586 }
587 udp_proxies.push(UdpProxyConfig {
588 name: get_str(&map, &sec, "name"),
589 listen: get_str(&map, &sec, "listen"),
590 backends: get_array(&map, &sec, "backends"),
591 reply_timeout_ms: get_u64(&map, &sec, "reply_timeout_ms", 5000),
592 buffer_size: get_u64(&map, &sec, "buffer_size", 65536) as usize,
593 });
594 i += 1;
595 }
596
597 let mut ws_proxies = Vec::new();
599 let mut i = 0;
600 loop {
601 let sec = format!("ws_proxy[{}]", i);
602 if !section_exists(&map, &sec) {
603 break;
604 }
605 let hc_sec = format!("{}.health_check", sec);
606 let health_check = if section_exists(&map, &hc_sec) {
607 Some(HealthCheckConfig {
608 path: {
609 let p = get_str(&map, &hc_sec, "path");
610 if p.is_empty() { "/health".to_string() } else { p }
611 },
612 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
613 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
614 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
615 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
616 })
617 } else {
618 None
619 };
620 ws_proxies.push(WsProxyConfig {
621 name: get_str(&map, &sec, "name"),
622 listen: get_str(&map, &sec, "listen"),
623 backends: get_array(&map, &sec, "backends"),
624 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
625 read_timeout_ms: get_u64(&map, &sec, "read_timeout_ms", 30000),
626 health_check,
627 });
628 i += 1;
629 }
630
631 let global_middleware = parse_middleware_config(&map, "middleware", usize::MAX);
633
634 let authz = map.keys().any(|k| k == "authz" || k.starts_with("authz.")).then(|| {
636 let mut type_sections: Vec<&String> = map.keys().filter(|k| k.starts_with("authz.type.")).collect();
637 type_sections.sort();
638 AuthzConfig {
639 store: get_str(&map, "authz", "store"),
640 tuples: get_array(&map, "authz", "tuples"),
641 types: type_sections
642 .into_iter()
643 .map(|sec| {
644 let relations = map[sec].iter().map(|(k, _)| (k.clone(), get_str(&map, sec, k))).collect();
645 (sec["authz.type.".len()..].to_string(), relations)
646 })
647 .collect(),
648 }
649 });
650
651 ProxyConfig {
652 upstreams,
653 routes,
654 tcp_proxies,
655 udp_proxies,
656 ws_proxies,
657 global_middleware,
658 authz,
659 }
660 }
661}
662
663fn parse_middleware_config(
666 map: &parser::SectionMap,
667 mw_sec: &str,
668 route_idx: usize,
669) -> MiddlewareConfig {
670 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
671
672 let rl_sec = format!("{}.rate_limit", mw_sec);
673 let rate_limit = if section_exists(map, &rl_sec) {
674 Some(RateLimitConfig {
675 max_requests: get_u32(map, &rl_sec, "max_requests", 1000),
676 window_secs: get_u64(map, &rl_sec, "window_secs", 60),
677 })
678 } else {
679 None
680 };
681
682 let c_sec = format!("{}.cache", mw_sec);
683 let cache = if section_exists(map, &c_sec) {
684 Some(CacheConfig {
685 ttl_secs: get_u64(map, &c_sec, "ttl_secs", 60),
686 vary_by: get_array(map, &c_sec, "vary_by"),
687 dir: get_str(map, &c_sec, "dir"),
688 max_size: get_str(map, &c_sec, "max_size"),
689 purge_token_env: get_str(map, &c_sec, "purge_token_env"),
690 })
691 } else {
692 None
693 };
694
695 let a_sec = format!("{}.auth", mw_sec);
696 let auth = if section_exists(map, &a_sec) {
697 let auth_type = get_str(map, &a_sec, "type");
698 match auth_type.as_str() {
699 "bearer" => Some(AuthConfig::Bearer {
700 token_env: get_str(map, &a_sec, "token_env"),
701 }),
702 "jwt" => Some(AuthConfig::Jwt {
703 secret_env: get_str(map, &a_sec, "secret_env"),
704 previous_secret_env: get_str(map, &a_sec, "previous_secret_env"),
705 }),
706 "basic" => Some(AuthConfig::Basic {
707 htpasswd_file: get_str(map, &a_sec, "htpasswd_file"),
708 }),
709 _ => None,
710 }
711 } else {
712 None
713 };
714
715 let rewrite_request = collect_rewrite_rules(map, mw_sec, "request");
721 let rewrite_response = collect_rewrite_rules(map, mw_sec, "response");
722
723 let ip_sec = format!("{}.ip_filter", mw_sec);
724 let ip_allow = if section_exists(map, &ip_sec) {
725 get_array(map, &ip_sec, "allow")
726 } else {
727 vec![]
728 };
729 let ip_deny = if section_exists(map, &ip_sec) {
730 get_array(map, &ip_sec, "deny")
731 } else {
732 vec![]
733 };
734
735 let _ = route_idx; let timeout_ms = match get_u64(map, mw_sec, "timeout_ms", 0) {
741 0 => None,
742 ms => Some(ms),
743 };
744
745 let max_body_size = match get_u64(map, mw_sec, "max_body_size", 0) {
749 0 => None,
750 n => Some(n),
751 };
752
753 let w_sec = format!("{}.wasm", mw_sec);
754 let wasm = section_exists(map, &w_sec).then(|| WasmConfig {
755 module: get_str(map, &w_sec, "module"),
756 fuel: get_u64(map, &w_sec, "fuel", 50_000_000),
757 memory_mb: get_u64(map, &w_sec, "memory_mb", 16),
758 config: get_str(map, &w_sec, "config"),
759 });
760
761 let m_sec = format!("{}.mirror", mw_sec);
762 let mirror = section_exists(map, &m_sec).then(|| MirrorConfig {
763 upstream: get_str(map, &m_sec, "upstream"),
764 backends: get_array(map, &m_sec, "backends"),
765 percent: {
766 let raw = get_str(map, &m_sec, "percent");
767 if raw.is_empty() { 100.0 } else { raw.parse::<f64>().unwrap_or(f64::NAN) }
768 },
769 });
770
771 MiddlewareConfig {
772 rate_limit, cache, auth, rewrite_request, rewrite_response, ip_allow, ip_deny,
773 timeout_ms, max_body_size, wasm, mirror,
774 early_hints: get_array(map, mw_sec, "early_hints"),
775 authz: section_exists(map, &format!("{mw_sec}.authz")).then(|| RouteAuthzConfig {
776 relation: get_str(map, &format!("{mw_sec}.authz"), "relation"),
777 object: get_str(map, &format!("{mw_sec}.authz"), "object"),
778 }),
779 }
780}
781
782fn collect_rewrite_rules(
784 map: &parser::SectionMap,
785 mw_sec: &str,
786 direction: &str,
787) -> Vec<RewriteRuleConfig> {
788 use parser::{get_str, get_u64};
789
790 let mut rules = Vec::new();
791 let mut j = 0;
792 loop {
793 let rsec = format!("{}.rewrite.{}[{}]", mw_sec, direction, j);
794 if !parser::section_exists(map, &rsec) {
795 break;
796 }
797 let code_val = get_u64(map, &rsec, "code", 0);
798 rules.push(RewriteRuleConfig {
799 type_: get_str(map, &rsec, "type"),
800 name: {
801 let v = get_str(map, &rsec, "name");
802 if v.is_empty() { None } else { Some(v) }
803 },
804 value: {
805 let v = get_str(map, &rsec, "value");
806 if v.is_empty() { None } else { Some(v) }
807 },
808 prefix: {
809 let v = get_str(map, &rsec, "prefix");
810 if v.is_empty() { None } else { Some(v) }
811 },
812 from: {
813 let v = get_str(map, &rsec, "from");
814 if v.is_empty() { None } else { Some(v) }
815 },
816 to: {
817 let v = get_str(map, &rsec, "to");
818 if v.is_empty() { None } else { Some(v) }
819 },
820 code: if code_val == 0 { None } else { Some(code_val as u16) },
821 reason: {
822 let v = get_str(map, &rsec, "reason");
823 if v.is_empty() { None } else { Some(v) }
824 },
825 });
826 j += 1;
827 }
828 rules
829}
830
831pub(crate) fn config_file_path() -> String {
832 std::env::var("RWS_CONFIG_FILE").unwrap_or_else(|_| "rws.config.toml".to_string())
833}
834
835pub(crate) struct CompiledRoute {
839 pub(crate) matcher: RouteMatcher,
840 pub(crate) handler: Arc<dyn Application + Send + Sync>,
842 #[cfg_attr(not(feature = "http2"), allow(dead_code))]
846 pub(crate) async_proxy: Option<Arc<DynamicProxy>>,
847}
848
849#[derive(Clone, Default)]
851pub(crate) struct RouteMatcher {
852 pub(crate) host: Option<String>,
854 pub(crate) path_prefix: Option<String>,
856 pub(crate) path_exact: Option<String>,
858 pub(crate) method: Option<String>,
860 pub(crate) content_type_prefix: Option<String>,
862}
863
864impl RouteMatcher {
865 pub(crate) fn from_match_config(cfg: &MatchConfig) -> Self {
866 let (path_prefix, path_exact) = match &cfg.path {
867 Some(p) if p.ends_with('*') => {
868 let stripped = p.trim_end_matches('*').to_string();
870 (Some(stripped), None)
871 }
872 Some(p) => (None, Some(p.clone())),
873 None => (None, None),
874 };
875 let content_type_prefix = cfg.content_type.as_ref().map(|ct| {
876 if ct.ends_with('*') {
877 ct.trim_end_matches('*').to_string()
878 } else {
879 ct.clone()
880 }
881 });
882 RouteMatcher {
883 host: cfg.host.clone(),
884 path_prefix,
885 path_exact,
886 method: cfg.method.clone(),
887 content_type_prefix,
888 }
889 }
890
891 pub(crate) fn matches(&self, request: &Request, conn: &ConnectionInfo) -> bool {
893 if let Some(ref expected_host) = self.host {
895 if !host_matches(expected_host, request_host(request, conn)) {
896 return false;
897 }
898 }
899
900 if let Some(ref m) = self.method {
902 if request.method.to_uppercase() != m.as_str() {
903 return false;
904 }
905 }
906
907 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
909 if let Some(ref prefix) = self.path_prefix {
910 if !path.starts_with(prefix.as_str()) {
911 return false;
912 }
913 } else if let Some(ref exact) = self.path_exact {
914 if path != exact.as_str() {
915 return false;
916 }
917 }
918
919 if let Some(ref ct_prefix) = self.content_type_prefix {
921 let actual_ct = request
922 .headers
923 .iter()
924 .find(|h| h.name.eq_ignore_ascii_case("content-type"))
925 .map(|h| h.value.as_ref())
926 .unwrap_or("");
927 if !actual_ct.starts_with(ct_prefix.as_str()) {
928 return false;
929 }
930 }
931
932 true
933 }
934}
935
936#[derive(Clone)]
943pub struct ConfigDrivenApp {
944 current: Arc<RwLock<Arc<Generation>>>,
945 reload_lock: Arc<std::sync::Mutex<()>>,
947}
948
949struct Generation {
951 dispatch: RouteDispatch,
952 entry: Option<Arc<dyn Application + Send + Sync>>,
955 config: ProxyConfig,
956 source: Option<String>,
958 health_stop: Arc<AtomicBool>,
960}
961
962impl Generation {
963 fn new(routes: Arc<Vec<CompiledRoute>>, fallback: App, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
964 let dispatch = RouteDispatch { routes, fallback };
965 let global = &config.global_middleware;
966 let entry = if global.is_empty() && global.early_hints.is_empty() {
967 None
968 } else {
969 let mut entry = builder::apply_middleware(arc_app(dispatch.clone()), global);
970 if !global.early_hints.is_empty() {
973 let layer = crate::early_hints::EarlyHintsLayer::new(global.early_hints.iter().cloned());
974 entry = arc_app(crate::middleware::WithMiddleware::new(builder::ArcApp(entry)).wrap(layer));
975 }
976 Some(entry)
977 };
978 Generation { dispatch, entry, config, source, health_stop }
979 }
980}
981
982#[derive(Clone)]
984struct RouteDispatch {
985 routes: Arc<Vec<CompiledRoute>>,
986 fallback: App,
991}
992
993impl Application for RouteDispatch {
994 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
995 for route in self.routes.iter() {
996 if route.matcher.matches(request, conn) {
997 return route.handler.execute(request, conn);
998 }
999 }
1000 self.fallback.execute(request, conn)
1001 }
1002
1003 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1004 match self.routes.iter().find(|r| r.matcher.matches(request, conn)) {
1005 Some(route) => route.handler.streams_body(request, conn),
1006 None => self.fallback.streams_body(request, conn),
1007 }
1008 }
1009}
1010
1011impl MiddlewareConfig {
1012 pub fn is_empty(&self) -> bool {
1016 self.rate_limit.is_none() && self.cache.is_none() && self.auth.is_none()
1017 && self.rewrite_request.is_empty() && self.rewrite_response.is_empty()
1018 && self.ip_allow.is_empty() && self.ip_deny.is_empty()
1019 && self.timeout_ms.is_none() && self.max_body_size.is_none() && self.wasm.is_none()
1020 && self.authz.is_none()
1021 }
1022}
1023
1024#[derive(Debug, Clone, PartialEq)]
1026pub enum ReloadOutcome {
1027 Unchanged,
1029 Reloaded { routes: usize, warnings: Vec<String> },
1032}
1033
1034impl ConfigDrivenApp {
1035 pub(crate) fn new(routes: Vec<CompiledRoute>, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
1036 use crate::core::New;
1037 let generation = Generation::new(Arc::new(routes), App::new(), config, health_stop, source);
1038 ConfigDrivenApp {
1039 current: Arc::new(RwLock::new(Arc::new(generation))),
1040 reload_lock: Arc::new(std::sync::Mutex::new(())),
1041 }
1042 }
1043
1044 fn current(&self) -> Arc<Generation> {
1045 Arc::clone(&self.current.read().unwrap_or_else(|e| e.into_inner()))
1046 }
1047
1048 pub fn with_config(self, config: ServerConfig) -> Self {
1065 {
1066 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
1067 let old = Arc::clone(&slot);
1068 *slot = Arc::new(Generation::new(
1069 Arc::clone(&old.dispatch.routes),
1070 App::with_config(config),
1071 old.config.clone(),
1072 Arc::clone(&old.health_stop),
1073 old.source.clone(),
1074 ));
1075 }
1076 self
1077 }
1078
1079 pub fn reload_from_str(&self, toml: &str) -> Result<ReloadOutcome, crate::config_check::ConfigReport> {
1089 let _serialized = self.reload_lock.lock().unwrap_or_else(|e| e.into_inner());
1090 let old = self.current();
1091 if old.source.as_deref() == Some(toml) {
1092 return Ok(ReloadOutcome::Unchanged);
1093 }
1094 let config = ProxyConfig::from_str(toml);
1095 let mut report = crate::config_check::ConfigReport::default();
1096 validate::check_unknown_keys(toml, &mut report);
1097 config.validate(&mut report);
1098 if !report.is_ok() {
1099 return Err(report);
1100 }
1101
1102 let mut warnings = report.warnings;
1103 let listeners = |c: &ProxyConfig| format!("{:?}{:?}{:?}", c.tcp_proxies, c.udp_proxies, c.ws_proxies);
1104 if listeners(&config) != listeners(&old.config) {
1105 warnings.push("[[tcp_proxy]] / [[udp_proxy]] / [[ws_proxy]] changes need a restart; the old listeners keep running".to_string());
1106 }
1107
1108 let health_stop = Arc::new(AtomicBool::new(false));
1109 let routes = builder::compile_routes(&config, &health_stop);
1110 let route_count = routes.len();
1111 let generation = Generation::new(Arc::new(routes), old.dispatch.fallback.clone(), config, health_stop, Some(toml.to_string()));
1112 let replaced = {
1113 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
1114 std::mem::replace(&mut *slot, Arc::new(generation))
1115 };
1116 replaced.health_stop.store(true, Ordering::Relaxed);
1117 Ok(ReloadOutcome::Reloaded { routes: route_count, warnings })
1118 }
1119
1120 pub fn reload_from_file(&self) {
1125 let path = config_file_path();
1126 let toml = match std::fs::read_to_string(&path) {
1127 Ok(toml) => toml,
1128 Err(e) => {
1129 eprintln!("[proxy_config] reload: cannot read {path}: {e} — keeping the running routes");
1130 crate::config_reload::report_error(format!("cannot read {path}: {e}"));
1131 return;
1132 }
1133 };
1134 match self.reload_from_str(&toml) {
1135 Ok(ReloadOutcome::Unchanged) => {}
1136 Ok(ReloadOutcome::Reloaded { routes, warnings }) => {
1137 for w in &warnings {
1138 eprintln!("[proxy_config] Warning: {w}");
1139 }
1140 println!("[proxy_config] reloaded {path}: {routes} route{}", if routes == 1 { "" } else { "s" });
1141 }
1142 Err(report) => {
1143 for e in &report.errors {
1144 eprintln!("[proxy_config] reload ERROR: {e}");
1145 crate::config_reload::report_error(e.clone());
1146 }
1147 eprintln!("[proxy_config] {path} has errors — keeping the running routes");
1148 }
1149 }
1150 }
1151}
1152
1153fn normalized(request: &Request) -> std::borrow::Cow<'_, Request> {
1156 match normalize_request_target(&request.request_uri) {
1157 std::borrow::Cow::Borrowed(_) => std::borrow::Cow::Borrowed(request),
1158 std::borrow::Cow::Owned(target) => {
1159 let mut owned = request.clone();
1160 owned.request_uri = target;
1161 std::borrow::Cow::Owned(owned)
1162 }
1163 }
1164}
1165
1166pub(crate) fn normalize_request_target(target: &str) -> std::borrow::Cow<'_, str> {
1174 let (path, query) = match target.find('?') {
1175 Some(i) => (&target[..i], &target[i..]),
1176 None => (target, ""),
1177 };
1178 if !path.starts_with('/') || !(path.contains('%') || path.contains("//") || path.contains("/.")) {
1179 return std::borrow::Cow::Borrowed(target);
1180 }
1181 let bytes = path.as_bytes();
1182 let mut decoded = String::with_capacity(path.len());
1183 let mut i = 0;
1184 while i < bytes.len() {
1185 let hex = |b: u8| (b as char).to_digit(16);
1186 if bytes[i] == b'%' && i + 2 < bytes.len() {
1187 if let (Some(h), Some(l)) = (hex(bytes[i + 1]), hex(bytes[i + 2])) {
1188 let value = (h * 16 + l) as u8;
1189 if value.is_ascii_alphanumeric() || matches!(value, b'-' | b'.' | b'_' | b'~') {
1190 decoded.push(value as char);
1191 } else {
1192 decoded.push('%');
1193 decoded.push(bytes[i + 1].to_ascii_uppercase() as char);
1194 decoded.push(bytes[i + 2].to_ascii_uppercase() as char);
1195 }
1196 i += 3;
1197 continue;
1198 }
1199 }
1200 if bytes[i] == b'%' {
1204 decoded.push_str("%25");
1205 i += 1;
1206 continue;
1207 }
1208 let ch = path[i..].chars().next().unwrap_or('\u{FFFD}');
1210 decoded.push(ch);
1211 i += ch.len_utf8().max(1);
1212 }
1213 let mut segments: Vec<&str> = Vec::new();
1214 let parts: Vec<&str> = decoded.split('/').collect();
1215 let last = parts.len().saturating_sub(1);
1216 let mut trailing_slash = false;
1217 for (n, segment) in parts.iter().enumerate() {
1218 match *segment {
1219 "" => trailing_slash = n == last,
1220 "." => trailing_slash = n == last,
1221 ".." => {
1222 segments.pop();
1223 trailing_slash = n == last;
1224 }
1225 s => {
1226 segments.push(s);
1227 trailing_slash = false;
1228 }
1229 }
1230 }
1231 let mut out = String::with_capacity(decoded.len() + query.len());
1232 out.push('/');
1233 out.push_str(&segments.join("/"));
1234 if trailing_slash && !segments.is_empty() {
1235 out.push('/');
1236 }
1237 out.push_str(query);
1238 if out == target {
1239 std::borrow::Cow::Borrowed(target)
1240 } else {
1241 std::borrow::Cow::Owned(out)
1242 }
1243}
1244
1245impl Application for ConfigDrivenApp {
1246 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1247 let request = normalized(request);
1248 let generation = self.current();
1249 match &generation.entry {
1250 Some(entry) => entry.execute(&request, conn),
1251 None => generation.dispatch.execute(&request, conn),
1252 }
1253 }
1254
1255 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1258 let request = normalized(request);
1259 let generation = self.current();
1260 match &generation.entry {
1261 Some(entry) => entry.streams_body(&request, conn),
1262 None => generation.dispatch.streams_body(&request, conn),
1263 }
1264 }
1265
1266 fn runs_inline(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1270 let request = normalized(request);
1271 let generation = self.current();
1272 generation.entry.is_none()
1273 && !generation.dispatch.routes.iter().any(|r| r.matcher.matches(&request, conn))
1274 && App::is_inline_request(&request)
1275 }
1276
1277 #[cfg(feature = "http2")]
1281 fn execute_async<'a>(&'a self, request: &'a Request, conn: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
1282 let generation = self.current();
1283 if generation.entry.is_some() {
1284 return None;
1285 }
1286 let request = normalized(request);
1287 let route = generation.dispatch.routes.iter().find(|r| r.matcher.matches(&request, conn))?;
1288 let proxy = Arc::clone(route.async_proxy.as_ref()?);
1289 Some(Box::pin(async move { proxy.execute_async(&request, conn).await }))
1290 }
1291}
1292
1293#[cfg(feature = "http2")]
1299pub(crate) struct H2Route {
1300 live: Arc<RwLock<Vec<String>>>,
1301 tls: bool,
1302 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1303 connect_timeout: std::time::Duration,
1304 read_timeout: std::time::Duration,
1305 counter: std::sync::atomic::AtomicUsize,
1306}
1307
1308#[cfg(feature = "http2")]
1309impl H2Route {
1310 pub(crate) fn new(
1311 live: Arc<RwLock<Vec<String>>>,
1312 tls: bool,
1313 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1314 connect_timeout_ms: u64,
1315 read_timeout_ms: u64,
1316 ) -> H2Route {
1317 H2Route {
1318 live,
1319 tls,
1320 trust,
1321 connect_timeout: std::time::Duration::from_millis(connect_timeout_ms),
1322 read_timeout: std::time::Duration::from_millis(read_timeout_ms),
1323 counter: std::sync::atomic::AtomicUsize::new(0),
1324 }
1325 }
1326}
1327
1328#[cfg(feature = "http2")]
1329impl Application for H2Route {
1330 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1331 use crate::middleware::Middleware;
1332 crate::proxy::GrpcWebLayer::new().handle(request, conn, &H2Forward(self))
1333 }
1334
1335 fn streams_body(&self, _request: &Request, _conn: &ConnectionInfo) -> bool {
1336 true
1337 }
1338}
1339
1340#[cfg(feature = "http2")]
1342struct H2Forward<'a>(&'a H2Route);
1343
1344#[cfg(feature = "http2")]
1345impl Application for H2Forward<'_> {
1346 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1347 let route = self.0;
1348 let backends = route.live.read().unwrap_or_else(|e| e.into_inner()).clone();
1349 if backends.is_empty() {
1350 let mut response = crate::proxy::bad_gateway();
1351 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::DestinationUnavailable, None, None);
1352 return Ok(response);
1353 }
1354 let attempts = if matches!(request.body, crate::request::Body::Stream(_)) { 1 } else { backends.len() };
1356 let start = route.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1357 let mut failure: Option<(String, String)> = None;
1358 for attempt in 0..attempts {
1359 let url = &backends[(start + attempt) % backends.len()];
1360 let result = crate::proxy::forward_h2(
1361 request, crate::proxy::Peer::of(conn), url, route.tls, route.trust.as_deref(), route.connect_timeout, route.read_timeout,
1362 );
1363 match result {
1364 Ok(mut response) => {
1365 crate::proxy::status::received(&mut response, Some(url));
1366 return Ok(response);
1367 }
1368 Err(e) => {
1369 eprintln!("grpc route: {e}");
1370 failure = Some((e, url.clone()));
1371 }
1372 }
1373 }
1374 let mut response = crate::proxy::bad_gateway();
1375 if let Some((e, url)) = &failure {
1376 crate::proxy::status::failed_with(&mut response, e, Some(url));
1377 }
1378 Ok(response)
1379 }
1380}
1381
1382#[derive(Clone, Copy)]
1387pub(crate) struct NullApp;
1388
1389impl Application for NullApp {
1390 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1391 let mut r = Response::new();
1392 r.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1393 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1394 Ok(r)
1395 }
1396}
1397
1398use std::collections::HashMap;
1401use std::collections::hash_map::DefaultHasher;
1402use std::hash::{Hash, Hasher};
1403use std::sync::atomic::AtomicUsize;
1404use std::time::{Duration, SystemTime, UNIX_EPOCH};
1405
1406#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1410pub(crate) enum LoadBalanceStrategy {
1411 RoundRobin,
1412 Random,
1413 IpHash,
1414 LeastConnections,
1415}
1416
1417impl LoadBalanceStrategy {
1418 fn parse(s: &str) -> Self {
1419 match s {
1420 "random" => LoadBalanceStrategy::Random,
1421 "ip_hash" => LoadBalanceStrategy::IpHash,
1422 "least_connections" => LoadBalanceStrategy::LeastConnections,
1423 _ => LoadBalanceStrategy::RoundRobin,
1424 }
1425 }
1426}
1427
1428#[derive(Clone)]
1436pub(crate) struct DynamicProxy {
1437 live: Arc<RwLock<Vec<String>>>,
1438 counter: Arc<AtomicUsize>,
1439 connect_timeout: Duration,
1440 read_timeout: Duration,
1441 strip_prefix: Option<Arc<String>>,
1442 add_prefix: Option<Arc<String>>,
1443 tls: bool,
1444 strategy: LoadBalanceStrategy,
1445 connections: Arc<RwLock<HashMap<String, Arc<AtomicUsize>>>>,
1446 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1448 upstream: Arc<String>,
1450 policy: Option<Arc<upstream::UpstreamPolicy>>,
1452 mirror: Option<crate::proxy::mirror::Mirror>,
1455 early_hints: Option<Arc<Vec<String>>>,
1457 #[cfg(feature = "http3")]
1460 h3: Option<Arc<std::sync::Mutex<HashMap<String, std::time::Instant>>>>,
1461 upstream_host: UpstreamHost,
1463}
1464
1465impl DynamicProxy {
1466 pub(crate) fn with_upstream_host(mut self, host: UpstreamHost) -> Self {
1468 self.upstream_host = host;
1469 self
1470 }
1471
1472 fn host_override<'a>(&'a self, request: &'a Request) -> Option<&'a str> {
1474 match &self.upstream_host {
1475 UpstreamHost::Backend => None,
1476 UpstreamHost::Client => request.header(Header::_HOST).map(|h| h.value.as_ref()).filter(|v| !v.is_empty()),
1477 UpstreamHost::Fixed(host) => Some(host),
1478 }
1479 }
1480
1481 pub(crate) fn with_trust(mut self, trust: Option<Arc<crate::proxy::UpstreamTls>>) -> Self {
1483 self.trust = trust;
1484 self
1485 }
1486
1487 pub(crate) fn with_early_hints(mut self, links: &[String]) -> Self {
1492 self.early_hints = (!links.is_empty()).then(|| Arc::new(links.to_vec()));
1493 self
1494 }
1495
1496 #[cfg_attr(not(feature = "http3"), allow(unused_mut))]
1500 pub(crate) fn with_h3(mut self, enabled: bool) -> Self {
1501 #[cfg(feature = "http3")]
1502 {
1503 self.h3 = (enabled && self.tls).then(Default::default);
1504 }
1505 #[cfg(not(feature = "http3"))]
1506 let _ = enabled;
1507 self
1508 }
1509
1510 #[cfg(feature = "http3")]
1513 fn h3_target(&self, backend: &str) -> Option<Arc<rustls::ClientConfig>> {
1514 let tcp_until = self.h3.as_ref()?;
1515 let mut tcp_until = tcp_until.lock().unwrap_or_else(|e| e.into_inner());
1516 if let Some(until) = tcp_until.get(backend) {
1517 if std::time::Instant::now() < *until {
1518 return None;
1519 }
1520 tcp_until.remove(backend);
1521 }
1522 Some(match &self.trust {
1523 Some(trust) => trust.client_config(),
1524 None => crate::proxy::tls_client_config(),
1525 })
1526 }
1527
1528 #[cfg(feature = "http3")]
1530 fn fall_back_to_tcp(&self, backend: &str, error: &str) {
1531 if let Some(tcp_until) = &self.h3 {
1532 tcp_until.lock().unwrap_or_else(|e| e.into_inner()).insert(backend.to_string(), std::time::Instant::now() + H3_FALLBACK);
1533 }
1534 upstream::observe_h3_fallback(&self.upstream);
1535 eprintln!("upstream {}: HTTP/3 to {backend} failed ({error}); using TCP for {} s", self.upstream, H3_FALLBACK.as_secs());
1536 }
1537
1538 pub(crate) fn with_mirror(mut self, mirror: Option<crate::proxy::mirror::Mirror>) -> Self {
1540 self.mirror = mirror;
1541 self
1542 }
1543
1544 pub(crate) fn with_policy(mut self, upstream: &str, policy: Option<Arc<upstream::UpstreamPolicy>>) -> Self {
1545 self.upstream = Arc::new(upstream.to_string());
1546 self.policy = policy;
1547 self
1548 }
1549
1550 pub(crate) fn new(
1551 live: Arc<RwLock<Vec<String>>>,
1552 connect_timeout_ms: u64,
1553 read_timeout_ms: u64,
1554 strip_prefix: Option<String>,
1555 add_prefix: Option<String>,
1556 tls: bool,
1557 strategy: String,
1558 ) -> Self {
1559 DynamicProxy {
1560 live,
1561 counter: Arc::new(AtomicUsize::new(0)),
1562 connect_timeout: Duration::from_millis(connect_timeout_ms),
1563 read_timeout: Duration::from_millis(read_timeout_ms),
1564 strip_prefix: strip_prefix.map(Arc::new),
1565 add_prefix: add_prefix.map(Arc::new),
1566 tls,
1567 strategy: LoadBalanceStrategy::parse(&strategy),
1568 connections: Arc::new(RwLock::new(HashMap::new())),
1569 trust: None,
1570 upstream: Arc::new(String::new()),
1571 policy: None,
1572 mirror: None,
1573 early_hints: None,
1574 #[cfg(feature = "http3")]
1575 h3: None,
1576 upstream_host: UpstreamHost::Backend,
1577 }
1578 }
1579
1580 fn pick(&self, candidates: &[String], client_ip: &str) -> String {
1582 let policy = self.policy.as_deref().filter(|p| p.weighted());
1583 let idx = match self.strategy {
1584 LoadBalanceStrategy::RoundRobin => match policy {
1585 Some(policy) => return policy.weighted_round_robin(candidates).unwrap_or_else(|| candidates[0].clone()),
1586 None => self.counter.fetch_add(1, Ordering::Relaxed) % candidates.len(),
1587 },
1588 LoadBalanceStrategy::Random => {
1589 let nanos = SystemTime::now()
1590 .duration_since(UNIX_EPOCH)
1591 .map(|d| d.subsec_nanos())
1592 .unwrap_or(0) as usize;
1593 let salt = self.counter.fetch_add(1, Ordering::Relaxed);
1594 let roll = nanos.wrapping_add(salt.wrapping_mul(2_654_435_761));
1595 match policy {
1596 Some(policy) => policy.weighted_index(candidates, roll),
1597 None => roll % candidates.len(),
1598 }
1599 }
1600 LoadBalanceStrategy::IpHash => {
1601 let mut hasher = DefaultHasher::new();
1602 client_ip.hash(&mut hasher);
1603 (hasher.finish() as usize) % candidates.len()
1604 }
1605 LoadBalanceStrategy::LeastConnections => {
1606 let connections = self.connections.read().unwrap();
1607 let load = |backend: &String| connections.get(backend).map(|c| c.load(Ordering::Relaxed)).unwrap_or(0) as u64;
1608 let weight = |backend: &String| self.policy.as_deref().map_or(1, |p| p.weight(backend)) as u64;
1609 candidates
1611 .iter()
1612 .enumerate()
1613 .min_by(|(_, a), (_, b)| (load(a) * weight(b)).cmp(&(load(b) * weight(a))))
1614 .map(|(i, _)| i)
1615 .unwrap_or(0)
1616 }
1617 };
1618 candidates[idx].clone()
1619 }
1620
1621 fn next_backend(&self, request: &Request, client_ip: &str, tried: &[String]) -> Result<String, bool> {
1626 let mut candidates: Vec<String> = self.live.read().unwrap().iter().filter(|b| !tried.contains(b)).cloned().collect();
1627 if !candidates.is_empty() {
1628 candidates.retain(|b| !upstream::is_drained(&self.upstream, b));
1629 if candidates.is_empty() {
1630 return Err(true);
1632 }
1633 }
1634 let Some(policy) = self.policy.as_deref() else {
1635 return if candidates.is_empty() { Err(false) } else { Ok(self.pick(&candidates, client_ip)) };
1636 };
1637 if let Some(sticky) = &policy.sticky {
1638 if let Some(id) = upstream::cookie_value(request, &sticky.cookie) {
1639 if let Some(backend) = candidates.iter().find(|b| upstream::backend_id(b) == id).cloned() {
1640 if policy.admits(&backend) {
1641 return Ok(backend);
1642 }
1643 candidates.retain(|b| *b != backend);
1644 }
1645 }
1646 }
1647 let ejected = policy.any_open(&candidates);
1648 while !candidates.is_empty() {
1649 let backend = self.pick(&candidates, client_ip);
1650 if policy.admits(&backend) {
1651 return Ok(backend);
1652 }
1653 candidates.retain(|b| *b != backend);
1654 }
1655 Err(ejected)
1656 }
1657
1658 fn connection_counter(&self, backend: &str) -> Arc<AtomicUsize> {
1661 if let Some(counter) = self.connections.read().unwrap().get(backend) {
1662 return Arc::clone(counter);
1663 }
1664 let mut connections = self.connections.write().unwrap();
1665 Arc::clone(
1666 connections
1667 .entry(backend.to_string())
1668 .or_insert_with(|| Arc::new(AtomicUsize::new(0))),
1669 )
1670 }
1671}
1672
1673struct ConnectionGuard {
1676 counter: Arc<AtomicUsize>,
1677}
1678
1679impl Drop for ConnectionGuard {
1680 fn drop(&mut self) {
1681 self.counter.fetch_sub(1, Ordering::Relaxed);
1682 }
1683}
1684
1685struct Prepared<'a> {
1688 backend: String,
1689 host: String,
1690 port: u16,
1691 request: std::borrow::Cow<'a, Request>,
1692 _guard: Option<ConnectionGuard>,
1693}
1694
1695enum Attempt {
1697 Done(Response),
1699 Failed(Option<Response>),
1702}
1703
1704impl DynamicProxy {
1705 fn prepare<'a>(&self, request: &'a Request, conn: &ConnectionInfo, tried: &[String]) -> Result<Prepared<'a>, Response> {
1710 let backend = self.next_backend(request, &conn.client.ip, tried).map_err(|ejected| {
1711 let mut response = if ejected { service_unavailable() } else { bad_gateway() };
1712 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::DestinationUnavailable, None, None);
1713 response
1714 })?;
1715
1716 let guard = if self.strategy == LoadBalanceStrategy::LeastConnections {
1717 let counter = self.connection_counter(&backend);
1718 counter.fetch_add(1, Ordering::Relaxed);
1719 Some(ConnectionGuard { counter })
1720 } else {
1721 None
1722 };
1723
1724 let (host, port, _) = crate::proxy_config::health::parse_backend_url(&backend).ok_or_else(|| {
1725 let mut response = bad_gateway();
1726 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyConfigurationError, Some(&backend), None);
1727 response
1728 })?;
1729
1730 let request = if self.strip_prefix.is_some() || self.add_prefix.is_some() {
1732 let mut req_clone = request.clone();
1733 if let Some(ref sp) = self.strip_prefix {
1734 if let Some(stripped) = req_clone.request_uri.strip_prefix(sp.as_str()) {
1735 req_clone.request_uri = if stripped.is_empty() || !stripped.starts_with('/') {
1736 format!("/{}", stripped)
1737 } else {
1738 stripped.to_string()
1739 };
1740 }
1741 }
1742 if let Some(ref ap) = self.add_prefix {
1743 req_clone.request_uri = format!("{}{}", ap, req_clone.request_uri);
1744 }
1745 std::borrow::Cow::Owned(req_clone)
1746 } else {
1747 std::borrow::Cow::Borrowed(request)
1748 };
1749
1750 Ok(Prepared { backend, host, port, request, _guard: guard })
1751 }
1752
1753 fn settle(&self, backend: &str, result: Result<Response, String>, started: std::time::Instant, failure: &mut Option<(String, String)>) -> Attempt {
1755 let result = match result {
1756 Ok(mut response) => {
1757 crate::proxy::status::received(&mut response, Some(backend));
1758 Ok(response)
1759 }
1760 Err(e) => {
1761 *failure = Some((e, backend.to_string()));
1762 Err(())
1763 }
1764 };
1765 let status = result.as_ref().ok().map(|r| r.status_code);
1766 upstream::observe(&self.upstream, backend, status, started);
1767 let failed = status.is_none_or(|s| matches!(s, 502..=504));
1768 if let Some(policy) = &self.policy {
1769 policy.record(backend, failed);
1770 }
1771 match result {
1772 Ok(response) if self.policy.as_deref().is_some_and(|p| p.retries_status(response.status_code)) => Attempt::Failed(Some(response)),
1773 Ok(response) => Attempt::Done(response),
1774 Err(()) => Attempt::Failed(None),
1775 }
1776 }
1777
1778 fn attempts(&self, request: &Request) -> u32 {
1780 match self.policy.as_deref().and_then(|p| p.retry.as_ref()) {
1781 Some(retry) if upstream::replayable(request) => 1 + retry.attempts,
1782 _ => 1,
1783 }
1784 }
1785
1786 fn stick(&self, request: &Request, backend: &str, response: &mut Response) {
1788 let Some(sticky) = self.policy.as_deref().and_then(|p| p.sticky.as_ref()) else { return };
1789 let id = upstream::backend_id(backend);
1790 if upstream::cookie_value(request, &sticky.cookie).as_deref() == Some(id.as_str()) {
1791 return;
1792 }
1793 let max_age = sticky.max_age_secs.map(|s| format!("; Max-Age={s}")).unwrap_or_default();
1794 response.headers.push(crate::header::Header {
1795 name: "Set-Cookie".into(),
1796 value: format!("{}={id}; Path=/; HttpOnly; SameSite=Lax{max_age}", sticky.cookie).into(),
1797 });
1798 }
1799
1800 #[cfg(feature = "http2")]
1803 pub(crate) async fn execute_async(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1804 if let Some(mirror) = &self.mirror {
1805 mirror.send(request, conn);
1806 }
1807 if let Some(links) = &self.early_hints {
1808 crate::early_hints::send_from(crate::early_hints::Source::Configured, links);
1809 }
1810 let mut tried: Vec<String> = Vec::new();
1811 let mut last: Option<Response> = None;
1812 let mut failure: Option<(String, String)> = None;
1813 for attempt in 0..self.attempts(request) {
1814 let prepared = match self.prepare(request, conn, &tried) {
1815 Ok(p) => p,
1816 Err(response) => return Ok(last.unwrap_or(response)),
1817 };
1818 if attempt > 0 {
1819 upstream::observe_retry(&self.upstream);
1820 }
1821 let tls = self.tls.then(|| match &self.trust {
1822 Some(trust) => trust.async_tls(),
1823 None => crate::proxy::async_upstream::AsyncTls {
1824 config: crate::proxy::tls_client_config(),
1825 pool: crate::proxy::async_upstream::default_tls_pool(),
1826 },
1827 });
1828 let started = std::time::Instant::now();
1829 #[cfg(feature = "http3")]
1830 if let Some(config) = self.h3_target(&prepared.backend) {
1831 let target = crate::proxy::h3_upstream::Target {
1832 host: &prepared.host,
1833 port: prepared.port,
1834 authority: self.host_override(request),
1835 tls: &config,
1836 connect_timeout: self.connect_timeout,
1837 read_timeout: self.read_timeout,
1838 };
1839 match crate::proxy::h3_upstream::forward_async(&prepared.request, crate::proxy::Peer::of(conn), &target).await {
1840 Err(crate::proxy::h3_upstream::H3Error::Connect(e)) => self.fall_back_to_tcp(&prepared.backend, &e),
1841 result => {
1842 match self.settle(&prepared.backend, result.map_err(|e| e.to_string()), started, &mut failure) {
1843 Attempt::Done(mut response) => {
1844 self.stick(request, &prepared.backend, &mut response);
1845 return Ok(response);
1846 }
1847 Attempt::Failed(response) => last = response,
1848 }
1849 tried.push(prepared.backend.clone());
1850 continue;
1851 }
1852 }
1853 }
1854 let result = crate::proxy::async_upstream::forward_as(
1855 &prepared.request,
1856 crate::proxy::Peer::of(conn),
1857 &prepared.host,
1858 prepared.port,
1859 self.host_override(request).unwrap_or(&prepared.host),
1860 tls,
1861 self.connect_timeout,
1862 self.read_timeout,
1863 )
1864 .await;
1865 match self.settle(&prepared.backend, result, started, &mut failure) {
1866 Attempt::Done(mut response) => {
1867 self.stick(request, &prepared.backend, &mut response);
1868 return Ok(response);
1869 }
1870 Attempt::Failed(response) => last = response,
1871 }
1872 tried.push(prepared.backend.clone());
1873 }
1874 Ok(last.unwrap_or_else(|| {
1875 let mut response = bad_gateway();
1876 match &failure {
1877 Some((e, backend)) => crate::proxy::status::failed_with(&mut response, e, Some(backend)),
1878 None => crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyInternalError, None, None),
1879 }
1880 response
1881 }))
1882 }
1883
1884 pub(crate) fn supports_async(&self) -> bool {
1886 cfg!(feature = "http2")
1887 }
1888}
1889
1890impl Application for DynamicProxy {
1891 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1892 if let Some(mirror) = &self.mirror {
1893 mirror.send(request, conn);
1894 }
1895 if let Some(links) = &self.early_hints {
1896 crate::early_hints::send_from(crate::early_hints::Source::Configured, links);
1897 }
1898 let mut tried: Vec<String> = Vec::new();
1899 let mut last: Option<Response> = None;
1900 let mut failure: Option<(String, String)> = None;
1901 for attempt in 0..self.attempts(request) {
1902 let prepared = match self.prepare(request, conn, &tried) {
1903 Ok(p) => p,
1904 Err(response) => return Ok(last.unwrap_or(response)),
1905 };
1906 if attempt > 0 {
1907 upstream::observe_retry(&self.upstream);
1908 }
1909 let started = std::time::Instant::now();
1912 #[cfg(feature = "http3")]
1913 if let Some(config) = self.h3_target(&prepared.backend) {
1914 let target = crate::proxy::h3_upstream::Target {
1915 host: &prepared.host,
1916 port: prepared.port,
1917 authority: self.host_override(request),
1918 tls: &config,
1919 connect_timeout: self.connect_timeout,
1920 read_timeout: self.read_timeout,
1921 };
1922 match crate::proxy::h3_upstream::forward(&prepared.request, crate::proxy::Peer::of(conn), &target) {
1923 Err(crate::proxy::h3_upstream::H3Error::Connect(e)) => self.fall_back_to_tcp(&prepared.backend, &e),
1924 result => {
1925 match self.settle(&prepared.backend, result.map_err(|e| e.to_string()), started, &mut failure) {
1926 Attempt::Done(mut response) => {
1927 self.stick(request, &prepared.backend, &mut response);
1928 return Ok(response);
1929 }
1930 Attempt::Failed(response) => last = response,
1931 }
1932 tried.push(prepared.backend.clone());
1933 continue;
1934 }
1935 }
1936 }
1937 let result = crate::proxy::forward_upstream_as(
1938 crate::proxy::shared_pool(),
1939 &prepared.request,
1940 crate::proxy::Peer::of(conn),
1941 &prepared.host,
1942 prepared.port,
1943 self.host_override(request).unwrap_or(&prepared.host),
1944 self.tls,
1945 self.trust.as_deref(),
1946 self.connect_timeout,
1947 self.read_timeout,
1948 );
1949 match self.settle(&prepared.backend, result, started, &mut failure) {
1950 Attempt::Done(mut response) => {
1951 self.stick(request, &prepared.backend, &mut response);
1952 return Ok(response);
1953 }
1954 Attempt::Failed(response) => last = response,
1955 }
1956 tried.push(prepared.backend.clone());
1957 }
1958 Ok(last.unwrap_or_else(|| {
1959 let mut response = bad_gateway();
1960 match &failure {
1961 Some((e, backend)) => crate::proxy::status::failed_with(&mut response, e, Some(backend)),
1962 None => crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyInternalError, None, None),
1963 }
1964 response
1965 }))
1966 }
1967}
1968
1969#[cfg(feature = "http3")]
1971pub(crate) const H3_FALLBACK: Duration = Duration::from_secs(300);
1972
1973fn service_unavailable() -> Response {
1974 let mut r = crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "every backend is out of rotation");
1975 r.headers.push(crate::header::Header { name: "Retry-After".into(), value: "5".into() });
1976 r
1977}
1978
1979fn bad_gateway() -> Response {
1980 crate::error::problem(STATUS_CODE_REASON_PHRASE.n502_bad_gateway, "")
1981}
1982
1983#[derive(Clone)]
1989pub(crate) struct RedirectAdapter {
1990 location_template: Arc<String>,
1991 status: i16,
1992 reason: Arc<String>,
1993}
1994
1995impl RedirectAdapter {
1996 pub(crate) fn new(location: String, status: u16) -> Self {
1997 let (code, reason) = redirect_status(status);
1998 RedirectAdapter {
1999 location_template: Arc::new(location),
2000 status: code,
2001 reason: Arc::new(reason),
2002 }
2003 }
2004}
2005
2006fn redirect_status(code: u16) -> (i16, String) {
2007 let phrase = match code {
2008 301 => STATUS_CODE_REASON_PHRASE.n301_moved_permanently.reason_phrase,
2009 302 => STATUS_CODE_REASON_PHRASE.n302_found.reason_phrase,
2010 307 => STATUS_CODE_REASON_PHRASE.n307_temporary_redirect.reason_phrase,
2011 308 => STATUS_CODE_REASON_PHRASE.n308_permanent_redirect.reason_phrase,
2012 _ => "Redirect",
2013 };
2014 (code as i16, phrase.to_string())
2015}
2016
2017impl Application for RedirectAdapter {
2018 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2019 let location = self
2020 .location_template
2021 .replace("$path", &request.request_uri);
2022 use crate::header::Header;
2023 let mut r = Response::new();
2024 r.status_code = self.status;
2025 r.reason_phrase = self.reason.as_ref().clone();
2026 r.headers.push(Header { name: "Location".into(), value: location.into() });
2027 Ok(r)
2028 }
2029}
2030
2031#[derive(Clone)]
2035pub(crate) struct RespondAdapter {
2036 status: i16,
2037 reason: Arc<String>,
2038 body: Arc<Vec<u8>>,
2039 content_type: Arc<String>,
2040}
2041
2042impl RespondAdapter {
2043 pub(crate) fn new(status: u16, body: String, content_type: String) -> Self {
2044 use crate::response::STATUS_CODE_REASON_PHRASE;
2045 let reason = match status {
2046 200 => STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
2047 201 => STATUS_CODE_REASON_PHRASE.n201_created.reason_phrase.to_string(),
2048 204 => STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string(),
2049 400 => STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string(),
2050 401 => STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string(),
2051 403 => STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string(),
2052 404 => STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string(),
2053 500 => STATUS_CODE_REASON_PHRASE.n500_internal_server_error.reason_phrase.to_string(),
2054 _ => "OK".to_string(),
2055 };
2056 RespondAdapter {
2057 status: status as i16,
2058 reason: Arc::new(reason),
2059 body: Arc::new(body.into_bytes()),
2060 content_type: Arc::new(content_type),
2061 }
2062 }
2063}
2064
2065impl Application for RespondAdapter {
2066 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2067 use crate::range::Range;
2068 let mut r = Response::new();
2069 r.status_code = self.status;
2070 r.reason_phrase = self.reason.as_ref().clone();
2071 if !self.body.is_empty() {
2072 r.content_range_list = vec![Range::get_content_range(
2073 self.body.as_ref().clone(),
2074 self.content_type.as_ref().clone(),
2075 )];
2076 }
2077 Ok(r)
2078 }
2079}
2080
2081#[derive(Clone)]
2090pub(crate) struct StaticAdapter {
2091 root: Arc<std::path::PathBuf>,
2092 index: Arc<Vec<String>>,
2093}
2094
2095impl StaticAdapter {
2096 pub(crate) fn new(root: String, index: Vec<String>) -> Self {
2097 let index = if index.is_empty() { vec!["index.html".to_string()] } else { index };
2098 StaticAdapter {
2099 root: Arc::new(std::path::PathBuf::from(root)),
2100 index: Arc::new(index),
2101 }
2102 }
2103
2104 fn resolve(&self, request_uri: &str) -> Option<std::path::PathBuf> {
2108 let raw_path = request_uri.split('?').next().unwrap_or(request_uri);
2109 let decoded = crate::url::URL::percent_decode(raw_path);
2110
2111 if decoded.split('/').any(|segment| segment == "..") {
2112 return None;
2113 }
2114
2115 let relative = decoded.trim_start_matches('/');
2116 Some(self.root.join(relative))
2117 }
2118}
2119
2120impl Application for StaticAdapter {
2121 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2122 let mut response = Response::new();
2123
2124 let not_found = |mut response: Response| {
2125 response.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
2126 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
2127 response
2128 };
2129
2130 let candidate = match self.resolve(&request.request_uri) {
2131 Some(p) => p,
2132 None => {
2133 response.status_code = *STATUS_CODE_REASON_PHRASE.n403_forbidden.status_code;
2134 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string();
2135 return Ok(response);
2136 }
2137 };
2138
2139 let mut file_path = candidate;
2140 if file_path.is_dir() {
2141 let indexed = self
2142 .index
2143 .iter()
2144 .map(|name| file_path.join(name))
2145 .find(|p| p.is_file());
2146
2147 file_path = match indexed {
2148 Some(p) => p,
2149 None => return Ok(not_found(response)),
2150 };
2151 }
2152
2153 if !file_path.is_file() {
2154 return Ok(not_found(response));
2155 }
2156
2157 if let (Ok(root_canon), Ok(file_canon)) =
2160 (self.root.canonicalize(), file_path.canonicalize())
2161 {
2162 if !file_canon.starts_with(&root_canon) {
2163 return Ok(not_found(response));
2164 }
2165 }
2166
2167 let path_str = match file_path.to_str() {
2168 Some(s) => s,
2169 None => return Ok(not_found(response)),
2170 };
2171
2172 match crate::range::Range::get_content_range_of_a_file(path_str) {
2173 Ok(content_range) => {
2174 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
2175 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
2176 response.content_range_list = vec![content_range];
2177 Ok(response)
2178 }
2179 Err(_) => Ok(not_found(response)),
2180 }
2181 }
2182}
2183
2184pub(crate) struct PerRouteRateLimit(pub(crate) Arc<crate::rate_limit::RateLimiter>);
2188
2189impl crate::middleware::Middleware for PerRouteRateLimit {
2190 fn handle(
2191 &self,
2192 request: &Request,
2193 conn: &ConnectionInfo,
2194 next: &dyn Application,
2195 ) -> Result<Response, String> {
2196 use crate::error::{AppError, IntoResponse};
2197 if self.0.check(&conn.client.ip) {
2198 next.execute(request, conn)
2199 } else {
2200 let mut response = AppError::TooManyRequests.into_response();
2201 let retry_after = self.0.retry_after(&conn.client.ip);
2202 response.headers.push(Header {
2203 name: Header::_RETRY_AFTER.into(),
2204 value: retry_after.to_string().into(),
2205 });
2206 Ok(response)
2207 }
2208 }
2209}
2210
2211pub(crate) struct PerRouteMaxBodySize(pub(crate) u64);
2226
2227impl crate::middleware::Middleware for PerRouteMaxBodySize {
2228 fn handle(
2229 &self,
2230 request: &Request,
2231 conn: &ConnectionInfo,
2232 next: &dyn Application,
2233 ) -> Result<Response, String> {
2234 use crate::error::{AppError, IntoResponse};
2235 if request.body.len() as u64 > self.0 {
2236 Ok(AppError::PayloadTooLarge(format!(
2237 "request body of {} bytes exceeds this route's {}-byte limit",
2238 request.body.len(),
2239 self.0,
2240 ))
2241 .into_response())
2242 } else {
2243 next.execute(request, conn)
2244 }
2245 }
2246}
2247
2248pub(crate) struct BearerAuthMiddleware {
2252 pub(crate) token: Arc<String>,
2253}
2254
2255impl crate::middleware::Middleware for BearerAuthMiddleware {
2256 fn handle(
2257 &self,
2258 request: &Request,
2259 conn: &ConnectionInfo,
2260 next: &dyn Application,
2261 ) -> Result<Response, String> {
2262 use crate::error::{AppError, IntoResponse};
2263 let expected = format!("Bearer {}", self.token);
2264 let authorized = request
2265 .headers
2266 .iter()
2267 .any(|h| h.name.eq_ignore_ascii_case("authorization") && constant_time_eq(h.value.as_bytes(), expected.as_bytes()));
2268 if authorized {
2269 next.execute(request, conn)
2270 } else {
2271 Ok(AppError::Unauthorized.into_response())
2272 }
2273 }
2274}
2275
2276pub(crate) use crate::core::constant_time_eq;
2277
2278pub(crate) struct AuthMisconfigured;
2281
2282pub(crate) fn cache_max_bytes(max_size: &str) -> Option<u64> {
2284 if max_size.trim().is_empty() {
2285 Some(256 * 1024 * 1024)
2286 } else {
2287 crate::log::access_sink::parse_size(max_size.trim()).filter(|n| *n > 0)
2288 }
2289}
2290
2291pub(crate) struct PluginMisconfigured;
2294
2295impl crate::middleware::Middleware for PluginMisconfigured {
2296 fn handle(&self, _request: &Request, _conn: &ConnectionInfo, _next: &dyn Application) -> Result<Response, String> {
2297 Ok(crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "this route's plugin is misconfigured"))
2298 }
2299}
2300
2301impl crate::middleware::Middleware for AuthMisconfigured {
2302 fn handle(
2303 &self,
2304 _request: &Request,
2305 _conn: &ConnectionInfo,
2306 _next: &dyn Application,
2307 ) -> Result<Response, String> {
2308 Ok(crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "authentication for this route is misconfigured"))
2309 }
2310}
2311
2312pub(crate) fn arc_app<A: Application + Send + Sync + 'static>(
2316 a: A,
2317) -> Arc<dyn Application + Send + Sync> {
2318 Arc::new(a)
2319}
2320
2321pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
2326 builder::build_from_file()
2327}