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#[cfg(feature = "sso")]
39pub(crate) mod oidc_route;
40
41#[cfg(test)]
42mod tests;
43
44use std::sync::atomic::{AtomicBool, Ordering};
45use std::sync::{Arc, RwLock};
46
47use crate::app::App;
48use crate::application::Application;
49use crate::core::New;
50use crate::request::Request;
51use crate::header::Header;
52use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
53use crate::router::{host_matches, request_host};
54use crate::server::ConnectionInfo;
55use crate::server_config::ServerConfig;
56
57#[derive(Debug, Clone)]
60pub struct ProxyConfig {
61 pub upstreams: Vec<UpstreamConfig>,
62 pub routes: Vec<RouteConfig>,
63 pub tcp_proxies: Vec<TcpProxyConfig>,
64 pub udp_proxies: Vec<UdpProxyConfig>,
65 pub ws_proxies: Vec<WsProxyConfig>,
66 pub global_middleware: MiddlewareConfig,
67 pub authz: Option<AuthzConfig>,
69}
70
71#[derive(Debug, Clone, Default)]
73pub struct AuthzConfig {
74 pub store: String,
76 pub tuples: Vec<String>,
78 pub types: Vec<(String, Vec<(String, String)>)>,
80}
81
82#[derive(Debug, Clone)]
84pub struct RouteAuthzConfig {
85 pub relation: String,
86 pub object: String,
88}
89
90#[derive(Debug, Clone)]
91pub struct UpstreamConfig {
92 pub name: String,
93 pub backends: Vec<String>,
94 pub strategy: String, pub health_check: Option<HealthCheckConfig>,
96 pub tls: bool,
100 pub tls_ca_file: Option<String>,
104 pub tls_client_cert_file: Option<String>,
108 pub tls_client_key_file: Option<String>,
109 pub weights: Vec<u32>,
111 pub sticky: Option<StickyConfig>,
113 pub circuit_breaker: Option<BreakerConfig>,
116 pub retry: Option<RetryConfig>,
118 pub protocol: String,
123}
124
125#[derive(Debug, Clone)]
126pub struct HealthCheckConfig {
127 pub path: String,
128 pub interval_secs: u64,
129 pub timeout_ms: u64,
130 pub healthy_threshold: u32,
131 pub unhealthy_threshold: u32,
132}
133
134#[derive(Debug, Clone)]
135pub struct RouteConfig {
136 pub name: String,
137 pub match_: MatchConfig,
138 pub action: ActionConfig,
139 pub middleware: MiddlewareConfig,
140}
141
142#[derive(Debug, Clone, Default)]
143pub struct MatchConfig {
144 pub host: Option<String>,
145 pub path: Option<String>,
146 pub method: Option<String>,
147 pub content_type: Option<String>,
148}
149
150#[derive(Debug, Clone, Default, PartialEq, Eq)]
152pub enum UpstreamHost {
153 #[default]
155 Backend,
156 Client,
159 Fixed(String),
161}
162
163#[derive(Debug, Clone)]
164pub enum ActionConfig {
165 Proxy {
166 upstream: String,
167 connect_timeout_ms: u64,
168 read_timeout_ms: u64,
169 strip_path_prefix: Option<String>,
170 add_path_prefix: Option<String>,
171 upstream_host: UpstreamHost,
173 },
174 Grpc {
175 upstream: String,
176 connect_timeout_ms: u64,
177 read_timeout_ms: u64,
178 },
179 Static {
180 root: String,
181 index: Vec<String>,
182 },
183 Redirect {
184 location: String,
185 status: u16,
186 },
187 Respond {
188 status: u16,
189 body: String,
190 content_type: String,
191 },
192 FastCgi(Box<FastCgiAction>),
194 Mcp(Box<mcp_gateway::McpGatewayConfig>),
196 A2a(Box<a2a_gateway::A2aGatewayConfig>),
198 Llm(Box<llm::LlmConfig>),
200 Unknown(String),
201}
202
203#[derive(Debug, Clone, Default, PartialEq)]
205pub struct FastCgiAction {
206 pub address: String,
207 pub root: String,
208 pub index: Vec<String>,
209 pub split_path: String,
210 pub front_controller: String,
211 pub params: Vec<(String, String)>,
212 pub connect_timeout_ms: u64,
213 pub read_timeout_ms: u64,
214 pub max_idle: u64,
215}
216
217#[derive(Debug, Clone, PartialEq)]
221pub struct WafSettings {
222 pub config: crate::waf::WafConfig,
223 pub errors: Vec<String>,
224}
225
226#[derive(Debug, Clone, Default)]
227pub struct MiddlewareConfig {
228 pub rate_limit: Option<RateLimitConfig>,
229 pub cache: Option<CacheConfig>,
230 pub auth: Option<AuthConfig>,
231 pub rewrite_request: Vec<RewriteRuleConfig>,
232 pub rewrite_response: Vec<RewriteRuleConfig>,
233 pub ip_allow: Vec<String>,
234 pub ip_deny: Vec<String>,
235 pub timeout_ms: Option<u64>,
240 pub max_body_size: Option<u64>,
256 pub wasm: Option<WasmConfig>,
259 pub early_hints: Vec<String>,
262 pub authz: Option<RouteAuthzConfig>,
265 pub mirror: Option<MirrorConfig>,
268 pub waf: Option<WafSettings>,
270}
271
272#[derive(Debug, Clone, Default, PartialEq)]
274pub struct MirrorConfig {
275 pub upstream: String,
277 pub backends: Vec<String>,
279 pub percent: f64,
281}
282
283#[derive(Debug, Clone, Default, PartialEq)]
285pub struct WasmConfig {
286 pub module: String,
288 pub fuel: u64,
289 pub memory_mb: u64,
290 pub config: String,
292}
293
294#[derive(Debug, Clone)]
295pub struct RateLimitConfig {
296 pub max_requests: u32,
297 pub window_secs: u64,
298}
299
300#[derive(Debug, Clone, Default)]
301pub struct CacheConfig {
302 pub ttl_secs: u64,
303 pub vary_by: Vec<String>,
304 pub dir: String,
306 pub max_size: String,
308 pub purge_token_env: String,
311}
312
313#[derive(Debug, Clone, PartialEq)]
314pub enum AuthConfig {
315 Basic { htpasswd_file: String },
320 Jwt { secret_env: String, previous_secret_env: String },
325 Bearer { token_env: String },
326 Oidc(Box<OidcRouteAuth>),
328 Forward { url: String, copy_headers: Vec<String>, timeout_ms: u64 },
331 Unknown(String),
334}
335
336#[derive(Debug, Clone, Default, PartialEq)]
338pub struct OidcRouteAuth {
339 pub issuer: String,
341 pub provider: String,
343 pub tenant: String,
345 pub client_id: String,
346 pub client_secret_env: String,
347 pub redirect_uri: String,
349 pub scopes: Vec<String>,
350 pub allowed_emails: Vec<String>,
351 pub allowed_domains: Vec<String>,
352 pub allowed_groups: Vec<String>,
353 pub groups_claim: String,
354 pub cookie_secret_env: String,
356 pub session_ttl_secs: u64,
357 pub exclude: Vec<String>,
358 pub forward_headers: bool,
359 pub path_prefix: String,
362}
363
364#[derive(Debug, Clone, Default)]
365pub struct RewriteRuleConfig {
366 pub type_: String,
367 pub name: Option<String>,
368 pub value: Option<String>,
369 pub prefix: Option<String>,
370 pub from: Option<String>,
371 pub to: Option<String>,
372 pub code: Option<u16>,
373 pub reason: Option<String>,
374}
375
376#[derive(Debug, Clone)]
377pub struct TcpProxyConfig {
378 pub name: String,
379 pub listen: String,
380 pub backends: Vec<String>,
381 pub connect_timeout_ms: u64,
382}
383
384#[derive(Debug, Clone)]
385pub struct UdpProxyConfig {
386 pub name: String,
387 pub listen: String,
388 pub backends: Vec<String>,
389 pub reply_timeout_ms: u64,
390 pub buffer_size: usize,
391}
392
393#[derive(Debug, Clone)]
394pub struct WsProxyConfig {
395 pub name: String,
396 pub listen: String,
397 pub backends: Vec<String>,
398 pub connect_timeout_ms: u64,
399 pub read_timeout_ms: u64,
400 pub health_check: Option<HealthCheckConfig>,
407}
408
409fn merge_action_keys(map: &mut parser::SectionMap, action_section: &str, action_type: &str) {
418 if action_type.is_empty() {
419 return;
420 }
421 let short_form: Vec<(String, String)> = match map.get(action_section) {
422 Some(entries) => entries.iter().filter(|(k, _)| k != "type").cloned().collect(),
423 None => return,
424 };
425 let nested = map.entry(format!("{action_section}.{action_type}")).or_default();
426 for (key, value) in short_form {
427 if !nested.iter().any(|(k, _)| *k == key) {
428 nested.push((key, value));
429 }
430 }
431}
432
433impl ProxyConfig {
434 pub fn is_proxy_mode() -> bool {
438 let path = config_file_path();
439 match std::fs::read_to_string(&path) {
440 Ok(contents) => {
441 contents.contains("[[route]]") || contents.contains("[[upstream]]")
442 }
443 Err(_) => false,
444 }
445 }
446
447 pub fn load() -> Self {
449 let path = config_file_path();
450 let contents = std::fs::read_to_string(&path).unwrap_or_default();
451 Self::from_str(&contents)
452 }
453
454 pub fn from_str(toml: &str) -> Self {
456 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
457
458 let mut map = parser::parse(toml);
459
460 let mut upstreams = Vec::new();
462 let mut i = 0;
463 loop {
464 let sec = format!("upstream[{}]", i);
465 if !section_exists(&map, &sec) {
466 break;
467 }
468 let name = get_str(&map, &sec, "name");
469 let backends = get_array(&map, &sec, "backends");
470 let strategy = {
471 let s = get_str(&map, &sec, "strategy");
472 if s.is_empty() { "round_robin".to_string() } else { s }
473 };
474 let hc_sec = format!("{}.health_check", sec);
475 let health_check = if section_exists(&map, &hc_sec) {
476 Some(HealthCheckConfig {
477 path: {
478 let p = get_str(&map, &hc_sec, "path");
479 if p.is_empty() { "/health".to_string() } else { p }
480 },
481 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
482 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
483 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
484 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
485 })
486 } else {
487 None
488 };
489 let tls = backends.iter().any(|b| ["https://", "h2s://", "grpcs://"].iter().any(|s| b.starts_with(s)));
490 let tls_ca_file = Some(get_str(&map, &sec, "tls_ca_file")).filter(|v| !v.is_empty());
491 let tls_client_cert_file = Some(get_str(&map, &sec, "tls_client_cert_file")).filter(|v| !v.is_empty());
492 let tls_client_key_file = Some(get_str(&map, &sec, "tls_client_key_file")).filter(|v| !v.is_empty());
493 let weights: Vec<u32> = get_array(&map, &sec, "weights").iter().map(|w| w.trim().parse().unwrap_or(0)).collect();
494 let sticky_sec = format!("{}.sticky", sec);
495 let sticky = section_exists(&map, &sticky_sec).then(|| StickyConfig {
496 cookie: Some(get_str(&map, &sticky_sec, "cookie")).filter(|c| !c.is_empty()).unwrap_or_else(|| "rws_backend".to_string()),
497 max_age_secs: Some(get_u64(&map, &sticky_sec, "max_age_secs", 0)).filter(|s| *s > 0),
498 });
499 let cb_sec = format!("{}.circuit_breaker", sec);
500 let circuit_breaker = section_exists(&map, &cb_sec).then(|| BreakerConfig {
501 failure_threshold: get_u32(&map, &cb_sec, "failure_threshold", 5),
502 recovery_secs: get_u64(&map, &cb_sec, "recovery_secs", 30),
503 half_open_probes: get_u32(&map, &cb_sec, "half_open_probes", 1),
504 });
505 let retry_sec = format!("{}.retry", sec);
506 let retry = section_exists(&map, &retry_sec).then(|| {
507 let status: Vec<i16> = get_array(&map, &retry_sec, "status").iter().filter_map(|s| s.trim().parse().ok()).collect();
508 RetryConfig { attempts: get_u32(&map, &retry_sec, "attempts", 1), status: if status.is_empty() { vec![502, 503, 504] } else { status } }
509 });
510 upstreams.push(UpstreamConfig {
511 name, backends, strategy, health_check, tls, tls_ca_file, tls_client_cert_file, tls_client_key_file,
512 weights, sticky, circuit_breaker, retry,
513 protocol: get_str(&map, &sec, "protocol"),
514 });
515 i += 1;
516 }
517
518 let mut routes = Vec::new();
520 let mut i = 0;
521 loop {
522 let sec = format!("route[{}]", i);
523 if !section_exists(&map, &sec) {
524 break;
525 }
526 let name = get_str(&map, &sec, "name");
527
528 let m_sec = format!("{}.match", sec);
530 let match_ = MatchConfig {
531 host: {
532 let h = get_str(&map, &m_sec, "host");
533 if h.is_empty() { None } else { Some(h) }
534 },
535 path: {
536 let p = get_str(&map, &m_sec, "path");
537 if p.is_empty() { None } else { Some(p) }
538 },
539 method: {
540 let m = get_str(&map, &m_sec, "method");
541 if m.is_empty() { None } else { Some(m.to_uppercase()) }
542 },
543 content_type: {
544 let c = get_str(&map, &m_sec, "content_type");
545 if c.is_empty() { None } else { Some(c) }
546 },
547 };
548
549 let a_sec = format!("{}.action", sec);
551 let action_type = get_str(&map, &a_sec, "type");
552 merge_action_keys(&mut map, &a_sec, &action_type);
553 let action = match action_type.as_str() {
554 "proxy" => {
555 let p_sec = format!("{}.action.proxy", sec);
556 ActionConfig::Proxy {
557 upstream: get_str(&map, &p_sec, "upstream"),
558 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
559 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
560 strip_path_prefix: {
561 let v = get_str(&map, &p_sec, "strip_path_prefix");
562 if v.is_empty() { None } else { Some(v) }
563 },
564 add_path_prefix: {
565 let v = get_str(&map, &p_sec, "add_path_prefix");
566 if v.is_empty() { None } else { Some(v) }
567 },
568 upstream_host: {
569 let fixed = get_str(&map, &p_sec, "upstream_host");
570 if !fixed.is_empty() {
571 UpstreamHost::Fixed(fixed)
572 } else if get_str(&map, &p_sec, "preserve_host") == "true" {
573 UpstreamHost::Client
574 } else {
575 UpstreamHost::Backend
576 }
577 },
578 }
579 }
580 "grpc" => {
581 let p_sec = format!("{}.action.grpc", sec);
582 ActionConfig::Grpc {
583 upstream: get_str(&map, &p_sec, "upstream"),
584 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
585 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
586 }
587 }
588 "static" => {
589 let s_sec = format!("{}.action.static", sec);
590 ActionConfig::Static {
591 root: get_str(&map, &s_sec, "root"),
592 index: get_array(&map, &s_sec, "index"),
593 }
594 }
595 "redirect" => {
596 let r_sec = format!("{}.action.redirect", sec);
597 ActionConfig::Redirect {
598 location: get_str(&map, &r_sec, "location"),
599 status: get_u64(&map, &r_sec, "status", 301) as u16,
600 }
601 }
602 "respond" => {
603 let r_sec = format!("{}.action.respond", sec);
604 ActionConfig::Respond {
605 status: get_u64(&map, &r_sec, "status", 200) as u16,
606 body: get_str(&map, &r_sec, "body"),
607 content_type: {
608 let ct = get_str(&map, &r_sec, "content_type");
609 if ct.is_empty() { "text/plain".to_string() } else { ct }
610 },
611 }
612 }
613 "fastcgi" => {
614 let f_sec = format!("{}.action.fastcgi", sec);
615 let or = |key: &str, default: &str| parser::get(&map, &f_sec, key).unwrap_or_else(|| default.to_string());
616 let mut params: Vec<(String, String)> = Vec::new();
617 for table in [format!("{a_sec}.params"), format!("{f_sec}.params")] {
618 for (k, v) in map.get(&table).cloned().unwrap_or_default() {
619 params.retain(|(name, _)| *name != k);
620 params.push((k, v));
621 }
622 }
623 let index = get_array(&map, &f_sec, "index");
624 ActionConfig::FastCgi(Box::new(FastCgiAction {
625 address: get_str(&map, &f_sec, "address"),
626 root: get_str(&map, &f_sec, "root"),
627 index: if index.is_empty() { vec!["index.php".to_string()] } else { index },
628 split_path: or("split_path", ".php"),
629 front_controller: or("front_controller", "index.php"),
630 params,
631 connect_timeout_ms: get_u64(&map, &f_sec, "connect_timeout_ms", 5000),
632 read_timeout_ms: get_u64(&map, &f_sec, "read_timeout_ms", 60000),
633 max_idle: get_u64(&map, &f_sec, "max_idle", 16),
634 }))
635 }
636 "mcp" => ActionConfig::Mcp(Box::new(mcp_gateway::parse(&map, &a_sec))),
637 "a2a" => ActionConfig::A2a(Box::new(a2a_gateway::parse(&map, &a_sec))),
638 "llm" => ActionConfig::Llm(Box::new(llm::parse(&map, &a_sec))),
639 other => ActionConfig::Unknown(other.to_string()),
640 };
641
642 let mw_sec = format!("{}.middleware", sec);
644 let middleware = parse_middleware_config(&map, &mw_sec, i);
645
646 routes.push(RouteConfig { name, match_, action, middleware });
647 i += 1;
648 }
649
650 let mut tcp_proxies = Vec::new();
652 let mut i = 0;
653 loop {
654 let sec = format!("tcp_proxy[{}]", i);
655 if !section_exists(&map, &sec) {
656 break;
657 }
658 tcp_proxies.push(TcpProxyConfig {
659 name: get_str(&map, &sec, "name"),
660 listen: get_str(&map, &sec, "listen"),
661 backends: get_array(&map, &sec, "backends"),
662 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
663 });
664 i += 1;
665 }
666
667 let mut udp_proxies = Vec::new();
669 let mut i = 0;
670 loop {
671 let sec = format!("udp_proxy[{}]", i);
672 if !section_exists(&map, &sec) {
673 break;
674 }
675 udp_proxies.push(UdpProxyConfig {
676 name: get_str(&map, &sec, "name"),
677 listen: get_str(&map, &sec, "listen"),
678 backends: get_array(&map, &sec, "backends"),
679 reply_timeout_ms: get_u64(&map, &sec, "reply_timeout_ms", 5000),
680 buffer_size: get_u64(&map, &sec, "buffer_size", 65536) as usize,
681 });
682 i += 1;
683 }
684
685 let mut ws_proxies = Vec::new();
687 let mut i = 0;
688 loop {
689 let sec = format!("ws_proxy[{}]", i);
690 if !section_exists(&map, &sec) {
691 break;
692 }
693 let hc_sec = format!("{}.health_check", sec);
694 let health_check = if section_exists(&map, &hc_sec) {
695 Some(HealthCheckConfig {
696 path: {
697 let p = get_str(&map, &hc_sec, "path");
698 if p.is_empty() { "/health".to_string() } else { p }
699 },
700 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
701 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
702 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
703 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
704 })
705 } else {
706 None
707 };
708 ws_proxies.push(WsProxyConfig {
709 name: get_str(&map, &sec, "name"),
710 listen: get_str(&map, &sec, "listen"),
711 backends: get_array(&map, &sec, "backends"),
712 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
713 read_timeout_ms: get_u64(&map, &sec, "read_timeout_ms", 30000),
714 health_check,
715 });
716 i += 1;
717 }
718
719 let global_middleware = parse_middleware_config(&map, "middleware", usize::MAX);
721
722 let authz = map.keys().any(|k| k == "authz" || k.starts_with("authz.")).then(|| {
724 let mut type_sections: Vec<&String> = map.keys().filter(|k| k.starts_with("authz.type.")).collect();
725 type_sections.sort();
726 AuthzConfig {
727 store: get_str(&map, "authz", "store"),
728 tuples: get_array(&map, "authz", "tuples"),
729 types: type_sections
730 .into_iter()
731 .map(|sec| {
732 let relations = map[sec].iter().map(|(k, _)| (k.clone(), get_str(&map, sec, k))).collect();
733 (sec["authz.type.".len()..].to_string(), relations)
734 })
735 .collect(),
736 }
737 });
738
739 ProxyConfig {
740 upstreams,
741 routes,
742 tcp_proxies,
743 udp_proxies,
744 ws_proxies,
745 global_middleware,
746 authz,
747 }
748 }
749}
750
751fn parse_middleware_config(
754 map: &parser::SectionMap,
755 mw_sec: &str,
756 route_idx: usize,
757) -> MiddlewareConfig {
758 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
759
760 let rl_sec = format!("{}.rate_limit", mw_sec);
761 let rate_limit = if section_exists(map, &rl_sec) {
762 Some(RateLimitConfig {
763 max_requests: get_u32(map, &rl_sec, "max_requests", 1000),
764 window_secs: get_u64(map, &rl_sec, "window_secs", 60),
765 })
766 } else {
767 None
768 };
769
770 let c_sec = format!("{}.cache", mw_sec);
771 let cache = if section_exists(map, &c_sec) {
772 Some(CacheConfig {
773 ttl_secs: get_u64(map, &c_sec, "ttl_secs", 60),
774 vary_by: get_array(map, &c_sec, "vary_by"),
775 dir: get_str(map, &c_sec, "dir"),
776 max_size: get_str(map, &c_sec, "max_size"),
777 purge_token_env: get_str(map, &c_sec, "purge_token_env"),
778 })
779 } else {
780 None
781 };
782
783 let a_sec = format!("{}.auth", mw_sec);
784 let auth = if section_exists(map, &a_sec) {
785 let auth_type = get_str(map, &a_sec, "type");
786 match auth_type.as_str() {
787 "bearer" => Some(AuthConfig::Bearer {
788 token_env: get_str(map, &a_sec, "token_env"),
789 }),
790 "jwt" => Some(AuthConfig::Jwt {
791 secret_env: get_str(map, &a_sec, "secret_env"),
792 previous_secret_env: get_str(map, &a_sec, "previous_secret_env"),
793 }),
794 "basic" => Some(AuthConfig::Basic {
795 htpasswd_file: get_str(map, &a_sec, "htpasswd_file"),
796 }),
797 "oidc" => Some(AuthConfig::Oidc(Box::new(OidcRouteAuth {
798 issuer: get_str(map, &a_sec, "issuer").trim_end_matches('/').to_string(),
799 provider: get_str(map, &a_sec, "provider").to_ascii_lowercase(),
800 tenant: get_str(map, &a_sec, "tenant"),
801 client_id: get_str(map, &a_sec, "client_id"),
802 client_secret_env: get_str(map, &a_sec, "client_secret_env"),
803 redirect_uri: get_str(map, &a_sec, "redirect_uri"),
804 scopes: get_array(map, &a_sec, "scopes"),
805 allowed_emails: get_array(map, &a_sec, "allowed_emails"),
806 allowed_domains: get_array(map, &a_sec, "allowed_domains"),
807 allowed_groups: get_array(map, &a_sec, "allowed_groups"),
808 groups_claim: get_str(map, &a_sec, "groups_claim"),
809 cookie_secret_env: get_str(map, &a_sec, "cookie_secret_env"),
810 session_ttl_secs: get_u64(map, &a_sec, "session_ttl_secs", 86_400),
811 exclude: get_array(map, &a_sec, "exclude"),
812 forward_headers: get_str(map, &a_sec, "forward_headers") != "false",
813 path_prefix: String::new(),
814 }))),
815 "forward" => Some(AuthConfig::Forward {
816 url: get_str(map, &a_sec, "url"),
817 copy_headers: get_array(map, &a_sec, "copy_headers"),
818 timeout_ms: get_u64(map, &a_sec, "timeout_ms", 5000),
819 }),
820 other => Some(AuthConfig::Unknown(other.to_string())),
821 }
822 } else {
823 None
824 };
825
826 let rewrite_request = collect_rewrite_rules(map, mw_sec, "request");
832 let rewrite_response = collect_rewrite_rules(map, mw_sec, "response");
833
834 let ip_sec = format!("{}.ip_filter", mw_sec);
835 let ip_allow = if section_exists(map, &ip_sec) {
836 get_array(map, &ip_sec, "allow")
837 } else {
838 vec![]
839 };
840 let ip_deny = if section_exists(map, &ip_sec) {
841 get_array(map, &ip_sec, "deny")
842 } else {
843 vec![]
844 };
845
846 let _ = route_idx; let timeout_ms = match get_u64(map, mw_sec, "timeout_ms", 0) {
852 0 => None,
853 ms => Some(ms),
854 };
855
856 let max_body_size = match get_u64(map, mw_sec, "max_body_size", 0) {
860 0 => None,
861 n => Some(n),
862 };
863
864 let w_sec = format!("{}.wasm", mw_sec);
865 let wasm = section_exists(map, &w_sec).then(|| WasmConfig {
866 module: get_str(map, &w_sec, "module"),
867 fuel: get_u64(map, &w_sec, "fuel", 50_000_000),
868 memory_mb: get_u64(map, &w_sec, "memory_mb", 16),
869 config: get_str(map, &w_sec, "config"),
870 });
871
872 let m_sec = format!("{}.mirror", mw_sec);
873 let mirror = section_exists(map, &m_sec).then(|| MirrorConfig {
874 upstream: get_str(map, &m_sec, "upstream"),
875 backends: get_array(map, &m_sec, "backends"),
876 percent: {
877 let raw = get_str(map, &m_sec, "percent");
878 if raw.is_empty() { 100.0 } else { raw.parse::<f64>().unwrap_or(f64::NAN) }
879 },
880 });
881
882 let waf = parse_waf(map, &format!("{mw_sec}.waf"));
883
884 MiddlewareConfig {
885 rate_limit, cache, auth, rewrite_request, rewrite_response, ip_allow, ip_deny,
886 timeout_ms, max_body_size, wasm, mirror, waf,
887 early_hints: get_array(map, mw_sec, "early_hints"),
888 authz: section_exists(map, &format!("{mw_sec}.authz")).then(|| RouteAuthzConfig {
889 relation: get_str(map, &format!("{mw_sec}.authz"), "relation"),
890 object: get_str(map, &format!("{mw_sec}.authz"), "object"),
891 }),
892 }
893}
894
895fn parse_waf(map: &parser::SectionMap, sec: &str) -> Option<WafSettings> {
898 use crate::waf::{Action, CustomRule, Exclusion, Field, Mode, WafConfig};
899 use parser::{get_array, get_str, get_u64, section_exists};
900 if !section_exists(map, sec) {
901 return None;
902 }
903 let mut errors = Vec::new();
904 let defaults = WafConfig::default();
905 let mode_text = get_str(map, sec, "mode");
906 let mode = Mode::parse(&mode_text).unwrap_or_else(|| {
907 errors.push(format!("[waf] mode = {mode_text:?}: expected block, detect or off"));
908 Mode::Block
909 });
910 let paranoia = get_u64(map, sec, "paranoia", 1);
911 if !(1..=3).contains(¶noia) {
912 errors.push(format!("[waf] paranoia = {paranoia}: expected 1, 2 or 3"));
913 }
914 let mut rules = Vec::new();
915 let mut j = 0;
916 loop {
917 let rsec = format!("{sec}.rule[{j}]");
918 if !section_exists(map, &rsec) {
919 break;
920 }
921 j += 1;
922 let id = get_u64(map, &rsec, "id", 0) as u32;
923 let field_text = get_str(map, &rsec, "field");
924 let Some(field) = Field::parse(&field_text) else {
925 errors.push(format!("[waf] rule {id}: field = {field_text:?}: expected path, query, args, arg:<name>, header:<name>, method, ip or body"));
926 continue;
927 };
928 let action_text = get_str(map, &rsec, "action");
929 let action = match action_text.to_ascii_lowercase().as_str() {
930 "deny" | "" => Action::Deny,
931 "allow" => Action::Allow,
932 "log" => Action::Log,
933 "score" => Action::Score(get_u64(map, &rsec, "score", 5) as u32),
934 other => {
935 errors.push(format!("[waf] rule {id}: action = {other:?}: expected deny, allow, log or score"));
936 continue;
937 }
938 };
939 let rule = CustomRule {
940 id,
941 field,
942 contains: get_array(map, &rsec, "contains"),
943 prefix: get_array(map, &rsec, "prefix"),
944 equals: get_array(map, &rsec, "equals"),
945 cidr: get_array(map, &rsec, "cidr"),
946 action,
947 };
948 if rule.contains.is_empty() && rule.prefix.is_empty() && rule.equals.is_empty() && rule.cidr.is_empty() {
949 errors.push(format!("[waf] rule {id}: needs contains, prefix, equals or cidr"));
950 continue;
951 }
952 if let Some(bad) = rule.cidr.iter().find(|c| !crate::ip_filter::is_valid_entry(c)) {
953 errors.push(format!("[waf] rule {id}: cidr {bad:?} is not an address or range"));
954 }
955 rules.push(rule);
956 }
957 Some(WafSettings {
958 config: WafConfig {
959 mode,
960 paranoia: paranoia.clamp(1, 3) as u8,
961 threshold: get_u64(map, sec, "threshold", defaults.threshold as u64).max(1) as u32,
962 inspect_body_bytes: get_u64(map, sec, "inspect_body_bytes", defaults.inspect_body_bytes as u64) as usize,
963 max_args: get_u64(map, sec, "max_args", defaults.max_args as u64) as usize,
964 exclude: get_array(map, sec, "exclude").iter().map(|e| Exclusion::parse(e)).collect(),
965 rules,
966 },
967 errors,
968 })
969}
970
971fn collect_rewrite_rules(
972 map: &parser::SectionMap,
973 mw_sec: &str,
974 direction: &str,
975) -> Vec<RewriteRuleConfig> {
976 use parser::{get_str, get_u64};
977
978 let mut rules = Vec::new();
979 let mut j = 0;
980 loop {
981 let rsec = format!("{}.rewrite.{}[{}]", mw_sec, direction, j);
982 if !parser::section_exists(map, &rsec) {
983 break;
984 }
985 let code_val = get_u64(map, &rsec, "code", 0);
986 rules.push(RewriteRuleConfig {
987 type_: get_str(map, &rsec, "type"),
988 name: {
989 let v = get_str(map, &rsec, "name");
990 if v.is_empty() { None } else { Some(v) }
991 },
992 value: {
993 let v = get_str(map, &rsec, "value");
994 if v.is_empty() { None } else { Some(v) }
995 },
996 prefix: {
997 let v = get_str(map, &rsec, "prefix");
998 if v.is_empty() { None } else { Some(v) }
999 },
1000 from: {
1001 let v = get_str(map, &rsec, "from");
1002 if v.is_empty() { None } else { Some(v) }
1003 },
1004 to: {
1005 let v = get_str(map, &rsec, "to");
1006 if v.is_empty() { None } else { Some(v) }
1007 },
1008 code: if code_val == 0 { None } else { Some(code_val as u16) },
1009 reason: {
1010 let v = get_str(map, &rsec, "reason");
1011 if v.is_empty() { None } else { Some(v) }
1012 },
1013 });
1014 j += 1;
1015 }
1016 rules
1017}
1018
1019pub(crate) fn config_file_path() -> String {
1020 std::env::var("RWS_CONFIG_FILE").unwrap_or_else(|_| "rws.config.toml".to_string())
1021}
1022
1023pub(crate) struct CompiledRoute {
1027 pub(crate) matcher: RouteMatcher,
1028 pub(crate) handler: Arc<dyn Application + Send + Sync>,
1030 #[cfg_attr(not(feature = "http2"), allow(dead_code))]
1034 pub(crate) async_proxy: Option<Arc<DynamicProxy>>,
1035}
1036
1037#[derive(Clone, Default)]
1039pub(crate) struct RouteMatcher {
1040 pub(crate) host: Option<String>,
1042 pub(crate) path_prefix: Option<String>,
1044 pub(crate) path_exact: Option<String>,
1046 pub(crate) method: Option<String>,
1048 pub(crate) content_type_prefix: Option<String>,
1050}
1051
1052impl RouteMatcher {
1053 pub(crate) fn from_match_config(cfg: &MatchConfig) -> Self {
1054 let (path_prefix, path_exact) = match &cfg.path {
1055 Some(p) if p.ends_with('*') => {
1056 let stripped = p.trim_end_matches('*').to_string();
1058 (Some(stripped), None)
1059 }
1060 Some(p) => (None, Some(p.clone())),
1061 None => (None, None),
1062 };
1063 let content_type_prefix = cfg.content_type.as_ref().map(|ct| {
1064 if ct.ends_with('*') {
1065 ct.trim_end_matches('*').to_string()
1066 } else {
1067 ct.clone()
1068 }
1069 });
1070 RouteMatcher {
1071 host: cfg.host.clone(),
1072 path_prefix,
1073 path_exact,
1074 method: cfg.method.clone(),
1075 content_type_prefix,
1076 }
1077 }
1078
1079 pub(crate) fn matches(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1081 if let Some(ref expected_host) = self.host {
1083 if !host_matches(expected_host, request_host(request, conn)) {
1084 return false;
1085 }
1086 }
1087
1088 if let Some(ref m) = self.method {
1090 if request.method.to_uppercase() != m.as_str() {
1091 return false;
1092 }
1093 }
1094
1095 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
1097 if let Some(ref prefix) = self.path_prefix {
1098 if !path.starts_with(prefix.as_str()) {
1099 return false;
1100 }
1101 } else if let Some(ref exact) = self.path_exact {
1102 if path != exact.as_str() {
1103 return false;
1104 }
1105 }
1106
1107 if let Some(ref ct_prefix) = self.content_type_prefix {
1109 let actual_ct = request
1110 .headers
1111 .iter()
1112 .find(|h| h.name.eq_ignore_ascii_case("content-type"))
1113 .map(|h| h.value.as_ref())
1114 .unwrap_or("");
1115 if !actual_ct.starts_with(ct_prefix.as_str()) {
1116 return false;
1117 }
1118 }
1119
1120 true
1121 }
1122}
1123
1124#[derive(Clone)]
1131pub struct ConfigDrivenApp {
1132 current: Arc<RwLock<Arc<Generation>>>,
1133 reload_lock: Arc<std::sync::Mutex<()>>,
1135}
1136
1137struct Generation {
1139 dispatch: RouteDispatch,
1140 entry: Option<Arc<dyn Application + Send + Sync>>,
1143 config: ProxyConfig,
1144 source: Option<String>,
1146 health_stop: Arc<AtomicBool>,
1148}
1149
1150impl Generation {
1151 fn new(routes: Arc<Vec<CompiledRoute>>, fallback: App, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
1152 let dispatch = RouteDispatch { routes, fallback };
1153 let global = &config.global_middleware;
1154 let entry = if global.is_empty() && global.early_hints.is_empty() {
1155 None
1156 } else {
1157 let mut entry = builder::apply_middleware(arc_app(dispatch.clone()), global);
1158 if !global.early_hints.is_empty() {
1161 let layer = crate::early_hints::EarlyHintsLayer::new(global.early_hints.iter().cloned());
1162 entry = arc_app(crate::middleware::WithMiddleware::new(builder::ArcApp(entry)).wrap(layer));
1163 }
1164 Some(entry)
1165 };
1166 Generation { dispatch, entry, config, source, health_stop }
1167 }
1168}
1169
1170#[derive(Clone)]
1172struct RouteDispatch {
1173 routes: Arc<Vec<CompiledRoute>>,
1174 fallback: App,
1179}
1180
1181impl Application for RouteDispatch {
1182 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1183 for route in self.routes.iter() {
1184 if route.matcher.matches(request, conn) {
1185 return route.handler.execute(request, conn);
1186 }
1187 }
1188 self.fallback.execute(request, conn)
1189 }
1190
1191 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1192 match self.routes.iter().find(|r| r.matcher.matches(request, conn)) {
1193 Some(route) => route.handler.streams_body(request, conn),
1194 None => self.fallback.streams_body(request, conn),
1195 }
1196 }
1197}
1198
1199impl MiddlewareConfig {
1200 pub fn is_empty(&self) -> bool {
1204 self.rate_limit.is_none() && self.cache.is_none() && self.auth.is_none()
1205 && self.rewrite_request.is_empty() && self.rewrite_response.is_empty()
1206 && self.ip_allow.is_empty() && self.ip_deny.is_empty()
1207 && self.timeout_ms.is_none() && self.max_body_size.is_none() && self.wasm.is_none()
1208 && self.authz.is_none() && self.waf.is_none()
1209 }
1210}
1211
1212#[derive(Debug, Clone, PartialEq)]
1214pub enum ReloadOutcome {
1215 Unchanged,
1217 Reloaded { routes: usize, warnings: Vec<String> },
1220}
1221
1222impl ConfigDrivenApp {
1223 pub(crate) fn new(routes: Vec<CompiledRoute>, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
1224 use crate::core::New;
1225 let generation = Generation::new(Arc::new(routes), App::new(), config, health_stop, source);
1226 ConfigDrivenApp {
1227 current: Arc::new(RwLock::new(Arc::new(generation))),
1228 reload_lock: Arc::new(std::sync::Mutex::new(())),
1229 }
1230 }
1231
1232 fn current(&self) -> Arc<Generation> {
1233 Arc::clone(&self.current.read().unwrap_or_else(|e| e.into_inner()))
1234 }
1235
1236 pub fn with_config(self, config: ServerConfig) -> Self {
1253 {
1254 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
1255 let old = Arc::clone(&slot);
1256 *slot = Arc::new(Generation::new(
1257 Arc::clone(&old.dispatch.routes),
1258 App::with_config(config),
1259 old.config.clone(),
1260 Arc::clone(&old.health_stop),
1261 old.source.clone(),
1262 ));
1263 }
1264 self
1265 }
1266
1267 pub fn reload_from_str(&self, toml: &str) -> Result<ReloadOutcome, crate::config_check::ConfigReport> {
1277 let _serialized = self.reload_lock.lock().unwrap_or_else(|e| e.into_inner());
1278 let old = self.current();
1279 if old.source.as_deref() == Some(toml) {
1280 return Ok(ReloadOutcome::Unchanged);
1281 }
1282 let config = ProxyConfig::from_str(toml);
1283 let mut report = crate::config_check::ConfigReport::default();
1284 validate::check_unknown_keys(toml, &mut report);
1285 config.validate(&mut report);
1286 if !report.is_ok() {
1287 return Err(report);
1288 }
1289
1290 let mut warnings = report.warnings;
1291 let listeners = |c: &ProxyConfig| format!("{:?}{:?}{:?}", c.tcp_proxies, c.udp_proxies, c.ws_proxies);
1292 if listeners(&config) != listeners(&old.config) {
1293 warnings.push("[[tcp_proxy]] / [[udp_proxy]] / [[ws_proxy]] changes need a restart; the old listeners keep running".to_string());
1294 }
1295
1296 let health_stop = Arc::new(AtomicBool::new(false));
1297 let routes = builder::compile_routes(&config, &health_stop);
1298 let route_count = routes.len();
1299 let generation = Generation::new(Arc::new(routes), old.dispatch.fallback.clone(), config, health_stop, Some(toml.to_string()));
1300 let replaced = {
1301 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
1302 std::mem::replace(&mut *slot, Arc::new(generation))
1303 };
1304 replaced.health_stop.store(true, Ordering::Relaxed);
1305 Ok(ReloadOutcome::Reloaded { routes: route_count, warnings })
1306 }
1307
1308 pub fn reload_from_file(&self) {
1313 let path = config_file_path();
1314 let toml = match std::fs::read_to_string(&path) {
1315 Ok(toml) => toml,
1316 Err(e) => {
1317 eprintln!("[proxy_config] reload: cannot read {path}: {e} — keeping the running routes");
1318 crate::config_reload::report_error(format!("cannot read {path}: {e}"));
1319 return;
1320 }
1321 };
1322 match self.reload_from_str(&toml) {
1323 Ok(ReloadOutcome::Unchanged) => {}
1324 Ok(ReloadOutcome::Reloaded { routes, warnings }) => {
1325 for w in &warnings {
1326 eprintln!("[proxy_config] Warning: {w}");
1327 }
1328 println!("[proxy_config] reloaded {path}: {routes} route{}", if routes == 1 { "" } else { "s" });
1329 }
1330 Err(report) => {
1331 for e in &report.errors {
1332 eprintln!("[proxy_config] reload ERROR: {e}");
1333 crate::config_reload::report_error(e.clone());
1334 }
1335 eprintln!("[proxy_config] {path} has errors — keeping the running routes");
1336 }
1337 }
1338 }
1339}
1340
1341fn normalized(request: &Request) -> std::borrow::Cow<'_, Request> {
1344 match normalize_request_target(&request.request_uri) {
1345 std::borrow::Cow::Borrowed(_) => std::borrow::Cow::Borrowed(request),
1346 std::borrow::Cow::Owned(target) => {
1347 let mut owned = request.clone();
1348 owned.request_uri = target;
1349 std::borrow::Cow::Owned(owned)
1350 }
1351 }
1352}
1353
1354pub(crate) fn normalize_request_target(target: &str) -> std::borrow::Cow<'_, str> {
1362 let (path, query) = match target.find('?') {
1363 Some(i) => (&target[..i], &target[i..]),
1364 None => (target, ""),
1365 };
1366 if !path.starts_with('/') || !(path.contains('%') || path.contains("//") || path.contains("/.")) {
1367 return std::borrow::Cow::Borrowed(target);
1368 }
1369 let bytes = path.as_bytes();
1370 let mut decoded = String::with_capacity(path.len());
1371 let mut i = 0;
1372 while i < bytes.len() {
1373 let hex = |b: u8| (b as char).to_digit(16);
1374 if bytes[i] == b'%' && i + 2 < bytes.len() {
1375 if let (Some(h), Some(l)) = (hex(bytes[i + 1]), hex(bytes[i + 2])) {
1376 let value = (h * 16 + l) as u8;
1377 if value.is_ascii_alphanumeric() || matches!(value, b'-' | b'.' | b'_' | b'~') {
1378 decoded.push(value as char);
1379 } else {
1380 decoded.push('%');
1381 decoded.push(bytes[i + 1].to_ascii_uppercase() as char);
1382 decoded.push(bytes[i + 2].to_ascii_uppercase() as char);
1383 }
1384 i += 3;
1385 continue;
1386 }
1387 }
1388 if bytes[i] == b'%' {
1392 decoded.push_str("%25");
1393 i += 1;
1394 continue;
1395 }
1396 let ch = path[i..].chars().next().unwrap_or('\u{FFFD}');
1398 decoded.push(ch);
1399 i += ch.len_utf8().max(1);
1400 }
1401 let mut segments: Vec<&str> = Vec::new();
1402 let parts: Vec<&str> = decoded.split('/').collect();
1403 let last = parts.len().saturating_sub(1);
1404 let mut trailing_slash = false;
1405 for (n, segment) in parts.iter().enumerate() {
1406 match *segment {
1407 "" => trailing_slash = n == last,
1408 "." => trailing_slash = n == last,
1409 ".." => {
1410 segments.pop();
1411 trailing_slash = n == last;
1412 }
1413 s => {
1414 segments.push(s);
1415 trailing_slash = false;
1416 }
1417 }
1418 }
1419 let mut out = String::with_capacity(decoded.len() + query.len());
1420 out.push('/');
1421 out.push_str(&segments.join("/"));
1422 if trailing_slash && !segments.is_empty() {
1423 out.push('/');
1424 }
1425 out.push_str(query);
1426 if out == target {
1427 std::borrow::Cow::Borrowed(target)
1428 } else {
1429 std::borrow::Cow::Owned(out)
1430 }
1431}
1432
1433impl Application for ConfigDrivenApp {
1434 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1435 let request = normalized(request);
1436 let generation = self.current();
1437 match &generation.entry {
1438 Some(entry) => entry.execute(&request, conn),
1439 None => generation.dispatch.execute(&request, conn),
1440 }
1441 }
1442
1443 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1446 let request = normalized(request);
1447 let generation = self.current();
1448 match &generation.entry {
1449 Some(entry) => entry.streams_body(&request, conn),
1450 None => generation.dispatch.streams_body(&request, conn),
1451 }
1452 }
1453
1454 fn runs_inline(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1458 let request = normalized(request);
1459 let generation = self.current();
1460 generation.entry.is_none()
1461 && !generation.dispatch.routes.iter().any(|r| r.matcher.matches(&request, conn))
1462 && App::is_inline_request(&request)
1463 }
1464
1465 #[cfg(feature = "http2")]
1469 fn execute_async<'a>(&'a self, request: &'a Request, conn: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
1470 let generation = self.current();
1471 if generation.entry.is_some() || crate::proxy::upgrade::is_upgrade(request) {
1473 return None;
1474 }
1475 let request = normalized(request);
1476 let route = generation.dispatch.routes.iter().find(|r| r.matcher.matches(&request, conn))?;
1477 let proxy = Arc::clone(route.async_proxy.as_ref()?);
1478 Some(Box::pin(async move { proxy.execute_async(&request, conn).await }))
1479 }
1480}
1481
1482#[cfg(feature = "http2")]
1488pub(crate) struct H2Route {
1489 live: Arc<RwLock<Vec<String>>>,
1490 tls: bool,
1491 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1492 connect_timeout: std::time::Duration,
1493 read_timeout: std::time::Duration,
1494 counter: std::sync::atomic::AtomicUsize,
1495}
1496
1497#[cfg(feature = "http2")]
1498impl H2Route {
1499 pub(crate) fn new(
1500 live: Arc<RwLock<Vec<String>>>,
1501 tls: bool,
1502 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1503 connect_timeout_ms: u64,
1504 read_timeout_ms: u64,
1505 ) -> H2Route {
1506 H2Route {
1507 live,
1508 tls,
1509 trust,
1510 connect_timeout: std::time::Duration::from_millis(connect_timeout_ms),
1511 read_timeout: std::time::Duration::from_millis(read_timeout_ms),
1512 counter: std::sync::atomic::AtomicUsize::new(0),
1513 }
1514 }
1515}
1516
1517#[cfg(feature = "http2")]
1518impl Application for H2Route {
1519 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1520 use crate::middleware::Middleware;
1521 crate::proxy::GrpcWebLayer::new().handle(request, conn, &H2Forward(self))
1522 }
1523
1524 fn streams_body(&self, _request: &Request, _conn: &ConnectionInfo) -> bool {
1525 true
1526 }
1527}
1528
1529#[cfg(feature = "http2")]
1531struct H2Forward<'a>(&'a H2Route);
1532
1533#[cfg(feature = "http2")]
1534impl Application for H2Forward<'_> {
1535 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1536 let route = self.0;
1537 let backends = route.live.read().unwrap_or_else(|e| e.into_inner()).clone();
1538 if backends.is_empty() {
1539 let mut response = crate::proxy::bad_gateway();
1540 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::DestinationUnavailable, None, None);
1541 return Ok(response);
1542 }
1543 let attempts = if matches!(request.body, crate::request::Body::Stream(_)) { 1 } else { backends.len() };
1545 let start = route.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1546 let mut failure: Option<(String, String)> = None;
1547 for attempt in 0..attempts {
1548 let url = &backends[(start + attempt) % backends.len()];
1549 let result = crate::proxy::forward_h2(
1550 request, crate::proxy::Peer::of(conn), url, route.tls, route.trust.as_deref(), route.connect_timeout, route.read_timeout,
1551 );
1552 match result {
1553 Ok(mut response) => {
1554 crate::proxy::status::received(&mut response, Some(url));
1555 return Ok(response);
1556 }
1557 Err(e) => {
1558 eprintln!("grpc route: {e}");
1559 failure = Some((e, url.clone()));
1560 }
1561 }
1562 }
1563 let mut response = crate::proxy::bad_gateway();
1564 if let Some((e, url)) = &failure {
1565 crate::proxy::status::failed_with(&mut response, e, Some(url));
1566 }
1567 Ok(response)
1568 }
1569}
1570
1571#[derive(Clone, Copy)]
1576pub(crate) struct NullApp;
1577
1578impl Application for NullApp {
1579 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1580 let mut r = Response::new();
1581 r.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1582 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1583 Ok(r)
1584 }
1585}
1586
1587use std::collections::HashMap;
1590use std::collections::hash_map::DefaultHasher;
1591use std::hash::{Hash, Hasher};
1592use std::sync::atomic::AtomicUsize;
1593use std::time::{Duration, SystemTime, UNIX_EPOCH};
1594
1595#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1599pub(crate) enum LoadBalanceStrategy {
1600 RoundRobin,
1601 Random,
1602 IpHash,
1603 LeastConnections,
1604}
1605
1606impl LoadBalanceStrategy {
1607 fn parse(s: &str) -> Self {
1608 match s {
1609 "random" => LoadBalanceStrategy::Random,
1610 "ip_hash" => LoadBalanceStrategy::IpHash,
1611 "least_connections" => LoadBalanceStrategy::LeastConnections,
1612 _ => LoadBalanceStrategy::RoundRobin,
1613 }
1614 }
1615}
1616
1617#[derive(Clone)]
1625pub(crate) struct DynamicProxy {
1626 live: Arc<RwLock<Vec<String>>>,
1627 counter: Arc<AtomicUsize>,
1628 connect_timeout: Duration,
1629 read_timeout: Duration,
1630 strip_prefix: Option<Arc<String>>,
1631 add_prefix: Option<Arc<String>>,
1632 tls: bool,
1633 strategy: LoadBalanceStrategy,
1634 connections: Arc<RwLock<HashMap<String, Arc<AtomicUsize>>>>,
1635 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1637 upstream: Arc<String>,
1639 policy: Option<Arc<upstream::UpstreamPolicy>>,
1641 mirror: Option<crate::proxy::mirror::Mirror>,
1644 early_hints: Option<Arc<Vec<String>>>,
1646 #[cfg(feature = "http3")]
1649 h3: Option<Arc<std::sync::Mutex<HashMap<String, std::time::Instant>>>>,
1650 upstream_host: UpstreamHost,
1652}
1653
1654impl DynamicProxy {
1655 pub(crate) fn with_upstream_host(mut self, host: UpstreamHost) -> Self {
1657 self.upstream_host = host;
1658 self
1659 }
1660
1661 fn host_override<'a>(&'a self, request: &'a Request) -> Option<&'a str> {
1663 match &self.upstream_host {
1664 UpstreamHost::Backend => None,
1665 UpstreamHost::Client => request.header(Header::_HOST).map(|h| h.value.as_ref()).filter(|v| !v.is_empty()),
1666 UpstreamHost::Fixed(host) => Some(host),
1667 }
1668 }
1669
1670 pub(crate) fn with_trust(mut self, trust: Option<Arc<crate::proxy::UpstreamTls>>) -> Self {
1672 self.trust = trust;
1673 self
1674 }
1675
1676 pub(crate) fn with_early_hints(mut self, links: &[String]) -> Self {
1681 self.early_hints = (!links.is_empty()).then(|| Arc::new(links.to_vec()));
1682 self
1683 }
1684
1685 #[cfg_attr(not(feature = "http3"), allow(unused_mut))]
1689 pub(crate) fn with_h3(mut self, enabled: bool) -> Self {
1690 #[cfg(feature = "http3")]
1691 {
1692 self.h3 = (enabled && self.tls).then(Default::default);
1693 }
1694 #[cfg(not(feature = "http3"))]
1695 let _ = enabled;
1696 self
1697 }
1698
1699 #[cfg(feature = "http3")]
1702 fn h3_target(&self, backend: &str) -> Option<Arc<rustls::ClientConfig>> {
1703 let tcp_until = self.h3.as_ref()?;
1704 let mut tcp_until = tcp_until.lock().unwrap_or_else(|e| e.into_inner());
1705 if let Some(until) = tcp_until.get(backend) {
1706 if std::time::Instant::now() < *until {
1707 return None;
1708 }
1709 tcp_until.remove(backend);
1710 }
1711 Some(match &self.trust {
1712 Some(trust) => trust.client_config(),
1713 None => crate::proxy::tls_client_config(),
1714 })
1715 }
1716
1717 #[cfg(feature = "http3")]
1719 fn fall_back_to_tcp(&self, backend: &str, error: &str) {
1720 if let Some(tcp_until) = &self.h3 {
1721 tcp_until.lock().unwrap_or_else(|e| e.into_inner()).insert(backend.to_string(), std::time::Instant::now() + H3_FALLBACK);
1722 }
1723 upstream::observe_h3_fallback(&self.upstream);
1724 eprintln!("upstream {}: HTTP/3 to {backend} failed ({error}); using TCP for {} s", self.upstream, H3_FALLBACK.as_secs());
1725 }
1726
1727 pub(crate) fn with_mirror(mut self, mirror: Option<crate::proxy::mirror::Mirror>) -> Self {
1729 self.mirror = mirror;
1730 self
1731 }
1732
1733 pub(crate) fn with_policy(mut self, upstream: &str, policy: Option<Arc<upstream::UpstreamPolicy>>) -> Self {
1734 self.upstream = Arc::new(upstream.to_string());
1735 self.policy = policy;
1736 self
1737 }
1738
1739 pub(crate) fn new(
1740 live: Arc<RwLock<Vec<String>>>,
1741 connect_timeout_ms: u64,
1742 read_timeout_ms: u64,
1743 strip_prefix: Option<String>,
1744 add_prefix: Option<String>,
1745 tls: bool,
1746 strategy: String,
1747 ) -> Self {
1748 DynamicProxy {
1749 live,
1750 counter: Arc::new(AtomicUsize::new(0)),
1751 connect_timeout: Duration::from_millis(connect_timeout_ms),
1752 read_timeout: Duration::from_millis(read_timeout_ms),
1753 strip_prefix: strip_prefix.map(Arc::new),
1754 add_prefix: add_prefix.map(Arc::new),
1755 tls,
1756 strategy: LoadBalanceStrategy::parse(&strategy),
1757 connections: Arc::new(RwLock::new(HashMap::new())),
1758 trust: None,
1759 upstream: Arc::new(String::new()),
1760 policy: None,
1761 mirror: None,
1762 early_hints: None,
1763 #[cfg(feature = "http3")]
1764 h3: None,
1765 upstream_host: UpstreamHost::Backend,
1766 }
1767 }
1768
1769 fn pick(&self, candidates: &[String], client_ip: &str) -> String {
1771 let policy = self.policy.as_deref().filter(|p| p.weighted());
1772 let idx = match self.strategy {
1773 LoadBalanceStrategy::RoundRobin => match policy {
1774 Some(policy) => return policy.weighted_round_robin(candidates).unwrap_or_else(|| candidates[0].clone()),
1775 None => self.counter.fetch_add(1, Ordering::Relaxed) % candidates.len(),
1776 },
1777 LoadBalanceStrategy::Random => {
1778 let nanos = SystemTime::now()
1779 .duration_since(UNIX_EPOCH)
1780 .map(|d| d.subsec_nanos())
1781 .unwrap_or(0) as usize;
1782 let salt = self.counter.fetch_add(1, Ordering::Relaxed);
1783 let roll = nanos.wrapping_add(salt.wrapping_mul(2_654_435_761));
1784 match policy {
1785 Some(policy) => policy.weighted_index(candidates, roll),
1786 None => roll % candidates.len(),
1787 }
1788 }
1789 LoadBalanceStrategy::IpHash => {
1790 let mut hasher = DefaultHasher::new();
1791 client_ip.hash(&mut hasher);
1792 (hasher.finish() as usize) % candidates.len()
1793 }
1794 LoadBalanceStrategy::LeastConnections => {
1795 let connections = self.connections.read().unwrap();
1796 let load = |backend: &String| connections.get(backend).map(|c| c.load(Ordering::Relaxed)).unwrap_or(0) as u64;
1797 let weight = |backend: &String| self.policy.as_deref().map_or(1, |p| p.weight(backend)) as u64;
1798 candidates
1800 .iter()
1801 .enumerate()
1802 .min_by(|(_, a), (_, b)| (load(a) * weight(b)).cmp(&(load(b) * weight(a))))
1803 .map(|(i, _)| i)
1804 .unwrap_or(0)
1805 }
1806 };
1807 candidates[idx].clone()
1808 }
1809
1810 fn next_backend(&self, request: &Request, client_ip: &str, tried: &[String]) -> Result<String, bool> {
1815 let mut candidates: Vec<String> = self.live.read().unwrap().iter().filter(|b| !tried.contains(b)).cloned().collect();
1816 if !candidates.is_empty() {
1817 candidates.retain(|b| !upstream::is_drained(&self.upstream, b));
1818 if candidates.is_empty() {
1819 return Err(true);
1821 }
1822 }
1823 let Some(policy) = self.policy.as_deref() else {
1824 return if candidates.is_empty() { Err(false) } else { Ok(self.pick(&candidates, client_ip)) };
1825 };
1826 if let Some(sticky) = &policy.sticky {
1827 if let Some(id) = upstream::cookie_value(request, &sticky.cookie) {
1828 if let Some(backend) = candidates.iter().find(|b| upstream::backend_id(b) == id).cloned() {
1829 if policy.admits(&backend) {
1830 return Ok(backend);
1831 }
1832 candidates.retain(|b| *b != backend);
1833 }
1834 }
1835 }
1836 let ejected = policy.any_open(&candidates);
1837 while !candidates.is_empty() {
1838 let backend = self.pick(&candidates, client_ip);
1839 if policy.admits(&backend) {
1840 return Ok(backend);
1841 }
1842 candidates.retain(|b| *b != backend);
1843 }
1844 Err(ejected)
1845 }
1846
1847 fn connection_counter(&self, backend: &str) -> Arc<AtomicUsize> {
1850 if let Some(counter) = self.connections.read().unwrap().get(backend) {
1851 return Arc::clone(counter);
1852 }
1853 let mut connections = self.connections.write().unwrap();
1854 Arc::clone(
1855 connections
1856 .entry(backend.to_string())
1857 .or_insert_with(|| Arc::new(AtomicUsize::new(0))),
1858 )
1859 }
1860}
1861
1862struct ConnectionGuard {
1865 counter: Arc<AtomicUsize>,
1866}
1867
1868impl Drop for ConnectionGuard {
1869 fn drop(&mut self) {
1870 self.counter.fetch_sub(1, Ordering::Relaxed);
1871 }
1872}
1873
1874struct Prepared<'a> {
1877 backend: String,
1878 host: String,
1879 port: u16,
1880 request: std::borrow::Cow<'a, Request>,
1881 _guard: Option<ConnectionGuard>,
1882}
1883
1884enum Attempt {
1886 Done(Response),
1888 Failed(Option<Response>),
1891}
1892
1893impl DynamicProxy {
1894 fn forward_upgrade(&self, request: &Request, conn: &ConnectionInfo) -> Response {
1897 let prepared = match self.prepare(request, conn, &[]) {
1898 Ok(p) => p,
1899 Err(response) => return response,
1900 };
1901 if self.tls || prepared.backend.starts_with("https://") || prepared.backend.starts_with("wss://") {
1902 eprintln!("[proxy] upgrade to {}: TLS backends can't take upgraded connections yet", prepared.backend);
1903 return bad_gateway();
1904 }
1905 let default_host = if prepared.port == 80 { prepared.host.clone() } else { format!("{}:{}", prepared.host, prepared.port) };
1906 let host_header = self.host_override(request).map(str::to_string).unwrap_or(default_host);
1907 match crate::proxy::upgrade::forward(&prepared.request, crate::proxy::Peer::of(conn), &prepared.host, prepared.port, &host_header, self.read_timeout) {
1908 Ok(response) => response,
1909 Err(e) => {
1910 eprintln!("[proxy] upgrade to {}: {e}", prepared.backend);
1911 let mut response = bad_gateway();
1912 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ConnectionRefused, Some(&prepared.backend), None);
1913 response
1914 }
1915 }
1916 }
1917
1918 fn prepare<'a>(&self, request: &'a Request, conn: &ConnectionInfo, tried: &[String]) -> Result<Prepared<'a>, Response> {
1923 let backend = self.next_backend(request, &conn.client.ip, tried).map_err(|ejected| {
1924 let mut response = if ejected { service_unavailable() } else { bad_gateway() };
1925 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::DestinationUnavailable, None, None);
1926 response
1927 })?;
1928
1929 let guard = if self.strategy == LoadBalanceStrategy::LeastConnections {
1930 let counter = self.connection_counter(&backend);
1931 counter.fetch_add(1, Ordering::Relaxed);
1932 Some(ConnectionGuard { counter })
1933 } else {
1934 None
1935 };
1936
1937 let (host, port, _) = crate::proxy_config::health::parse_backend_url(&backend).ok_or_else(|| {
1938 let mut response = bad_gateway();
1939 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyConfigurationError, Some(&backend), None);
1940 response
1941 })?;
1942
1943 let request = if self.strip_prefix.is_some() || self.add_prefix.is_some() {
1945 let mut req_clone = request.clone();
1946 if let Some(ref sp) = self.strip_prefix {
1947 if let Some(stripped) = req_clone.request_uri.strip_prefix(sp.as_str()) {
1948 req_clone.request_uri = if stripped.is_empty() || !stripped.starts_with('/') {
1949 format!("/{}", stripped)
1950 } else {
1951 stripped.to_string()
1952 };
1953 }
1954 }
1955 if let Some(ref ap) = self.add_prefix {
1956 req_clone.request_uri = format!("{}{}", ap, req_clone.request_uri);
1957 }
1958 std::borrow::Cow::Owned(req_clone)
1959 } else {
1960 std::borrow::Cow::Borrowed(request)
1961 };
1962
1963 Ok(Prepared { backend, host, port, request, _guard: guard })
1964 }
1965
1966 fn settle(&self, backend: &str, result: Result<Response, String>, started: std::time::Instant, failure: &mut Option<(String, String)>) -> Attempt {
1968 let result = match result {
1969 Ok(mut response) => {
1970 crate::proxy::status::received(&mut response, Some(backend));
1971 Ok(response)
1972 }
1973 Err(e) => {
1974 *failure = Some((e, backend.to_string()));
1975 Err(())
1976 }
1977 };
1978 let status = result.as_ref().ok().map(|r| r.status_code);
1979 upstream::observe(&self.upstream, backend, status, started);
1980 let failed = status.is_none_or(|s| matches!(s, 502..=504));
1981 if let Some(policy) = &self.policy {
1982 policy.record(backend, failed);
1983 }
1984 match result {
1985 Ok(response) if self.policy.as_deref().is_some_and(|p| p.retries_status(response.status_code)) => Attempt::Failed(Some(response)),
1986 Ok(response) => Attempt::Done(response),
1987 Err(()) => Attempt::Failed(None),
1988 }
1989 }
1990
1991 fn attempts(&self, request: &Request) -> u32 {
1993 match self.policy.as_deref().and_then(|p| p.retry.as_ref()) {
1994 Some(retry) if upstream::replayable(request) => 1 + retry.attempts,
1995 _ => 1,
1996 }
1997 }
1998
1999 fn stick(&self, request: &Request, backend: &str, response: &mut Response) {
2001 let Some(sticky) = self.policy.as_deref().and_then(|p| p.sticky.as_ref()) else { return };
2002 let id = upstream::backend_id(backend);
2003 if upstream::cookie_value(request, &sticky.cookie).as_deref() == Some(id.as_str()) {
2004 return;
2005 }
2006 let max_age = sticky.max_age_secs.map(|s| format!("; Max-Age={s}")).unwrap_or_default();
2007 response.headers.push(crate::header::Header {
2008 name: "Set-Cookie".into(),
2009 value: format!("{}={id}; Path=/; HttpOnly; SameSite=Lax{max_age}", sticky.cookie).into(),
2010 });
2011 }
2012
2013 #[cfg(feature = "http2")]
2016 pub(crate) async fn execute_async(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
2017 if let Some(mirror) = &self.mirror {
2018 mirror.send(request, conn);
2019 }
2020 if let Some(links) = &self.early_hints {
2021 crate::early_hints::send_from(crate::early_hints::Source::Configured, links);
2022 }
2023 let mut tried: Vec<String> = Vec::new();
2024 let mut last: Option<Response> = None;
2025 let mut failure: Option<(String, String)> = None;
2026 for attempt in 0..self.attempts(request) {
2027 let prepared = match self.prepare(request, conn, &tried) {
2028 Ok(p) => p,
2029 Err(response) => return Ok(last.unwrap_or(response)),
2030 };
2031 if attempt > 0 {
2032 upstream::observe_retry(&self.upstream);
2033 }
2034 let tls = self.tls.then(|| match &self.trust {
2035 Some(trust) => trust.async_tls(),
2036 None => crate::proxy::async_upstream::AsyncTls {
2037 config: crate::proxy::tls_client_config(),
2038 pool: crate::proxy::async_upstream::default_tls_pool(),
2039 },
2040 });
2041 let started = std::time::Instant::now();
2042 #[cfg(feature = "http3")]
2043 if let Some(config) = self.h3_target(&prepared.backend) {
2044 let target = crate::proxy::h3_upstream::Target {
2045 host: &prepared.host,
2046 port: prepared.port,
2047 authority: self.host_override(request),
2048 tls: &config,
2049 connect_timeout: self.connect_timeout,
2050 read_timeout: self.read_timeout,
2051 };
2052 match crate::proxy::h3_upstream::forward_async(&prepared.request, crate::proxy::Peer::of(conn), &target).await {
2053 Err(crate::proxy::h3_upstream::H3Error::Connect(e)) => self.fall_back_to_tcp(&prepared.backend, &e),
2054 result => {
2055 match self.settle(&prepared.backend, result.map_err(|e| e.to_string()), started, &mut failure) {
2056 Attempt::Done(mut response) => {
2057 self.stick(request, &prepared.backend, &mut response);
2058 return Ok(response);
2059 }
2060 Attempt::Failed(response) => last = response,
2061 }
2062 tried.push(prepared.backend.clone());
2063 continue;
2064 }
2065 }
2066 }
2067 let result = crate::proxy::async_upstream::forward_as(
2068 &prepared.request,
2069 crate::proxy::Peer::of(conn),
2070 &prepared.host,
2071 prepared.port,
2072 self.host_override(request).unwrap_or(&prepared.host),
2073 tls,
2074 self.connect_timeout,
2075 self.read_timeout,
2076 )
2077 .await;
2078 match self.settle(&prepared.backend, result, started, &mut failure) {
2079 Attempt::Done(mut response) => {
2080 self.stick(request, &prepared.backend, &mut response);
2081 return Ok(response);
2082 }
2083 Attempt::Failed(response) => last = response,
2084 }
2085 tried.push(prepared.backend.clone());
2086 }
2087 Ok(last.unwrap_or_else(|| {
2088 let mut response = bad_gateway();
2089 match &failure {
2090 Some((e, backend)) => crate::proxy::status::failed_with(&mut response, e, Some(backend)),
2091 None => crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyInternalError, None, None),
2092 }
2093 response
2094 }))
2095 }
2096
2097 pub(crate) fn supports_async(&self) -> bool {
2099 cfg!(feature = "http2")
2100 }
2101}
2102
2103impl Application for DynamicProxy {
2104 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
2105 if crate::proxy::upgrade::is_upgrade(request) {
2108 return Ok(self.forward_upgrade(request, conn));
2109 }
2110 if let Some(mirror) = &self.mirror {
2111 mirror.send(request, conn);
2112 }
2113 if let Some(links) = &self.early_hints {
2114 crate::early_hints::send_from(crate::early_hints::Source::Configured, links);
2115 }
2116 let mut tried: Vec<String> = Vec::new();
2117 let mut last: Option<Response> = None;
2118 let mut failure: Option<(String, String)> = None;
2119 for attempt in 0..self.attempts(request) {
2120 let prepared = match self.prepare(request, conn, &tried) {
2121 Ok(p) => p,
2122 Err(response) => return Ok(last.unwrap_or(response)),
2123 };
2124 if attempt > 0 {
2125 upstream::observe_retry(&self.upstream);
2126 }
2127 let started = std::time::Instant::now();
2130 #[cfg(feature = "http3")]
2131 if let Some(config) = self.h3_target(&prepared.backend) {
2132 let target = crate::proxy::h3_upstream::Target {
2133 host: &prepared.host,
2134 port: prepared.port,
2135 authority: self.host_override(request),
2136 tls: &config,
2137 connect_timeout: self.connect_timeout,
2138 read_timeout: self.read_timeout,
2139 };
2140 match crate::proxy::h3_upstream::forward(&prepared.request, crate::proxy::Peer::of(conn), &target) {
2141 Err(crate::proxy::h3_upstream::H3Error::Connect(e)) => self.fall_back_to_tcp(&prepared.backend, &e),
2142 result => {
2143 match self.settle(&prepared.backend, result.map_err(|e| e.to_string()), started, &mut failure) {
2144 Attempt::Done(mut response) => {
2145 self.stick(request, &prepared.backend, &mut response);
2146 return Ok(response);
2147 }
2148 Attempt::Failed(response) => last = response,
2149 }
2150 tried.push(prepared.backend.clone());
2151 continue;
2152 }
2153 }
2154 }
2155 let result = crate::proxy::forward_upstream_as(
2156 crate::proxy::shared_pool(),
2157 &prepared.request,
2158 crate::proxy::Peer::of(conn),
2159 &prepared.host,
2160 prepared.port,
2161 self.host_override(request).unwrap_or(&prepared.host),
2162 self.tls,
2163 self.trust.as_deref(),
2164 self.connect_timeout,
2165 self.read_timeout,
2166 );
2167 match self.settle(&prepared.backend, result, started, &mut failure) {
2168 Attempt::Done(mut response) => {
2169 self.stick(request, &prepared.backend, &mut response);
2170 return Ok(response);
2171 }
2172 Attempt::Failed(response) => last = response,
2173 }
2174 tried.push(prepared.backend.clone());
2175 }
2176 Ok(last.unwrap_or_else(|| {
2177 let mut response = bad_gateway();
2178 match &failure {
2179 Some((e, backend)) => crate::proxy::status::failed_with(&mut response, e, Some(backend)),
2180 None => crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyInternalError, None, None),
2181 }
2182 response
2183 }))
2184 }
2185}
2186
2187#[cfg(feature = "http3")]
2189pub(crate) const H3_FALLBACK: Duration = Duration::from_secs(300);
2190
2191fn service_unavailable() -> Response {
2192 let mut r = crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "every backend is out of rotation");
2193 r.headers.push(crate::header::Header { name: "Retry-After".into(), value: "5".into() });
2194 r
2195}
2196
2197fn bad_gateway() -> Response {
2198 crate::error::problem(STATUS_CODE_REASON_PHRASE.n502_bad_gateway, "")
2199}
2200
2201#[derive(Clone)]
2207pub(crate) struct RedirectAdapter {
2208 location_template: Arc<String>,
2209 status: i16,
2210 reason: Arc<String>,
2211}
2212
2213impl RedirectAdapter {
2214 pub(crate) fn new(location: String, status: u16) -> Self {
2215 let (code, reason) = redirect_status(status);
2216 RedirectAdapter {
2217 location_template: Arc::new(location),
2218 status: code,
2219 reason: Arc::new(reason),
2220 }
2221 }
2222}
2223
2224fn redirect_status(code: u16) -> (i16, String) {
2225 let phrase = match code {
2226 301 => STATUS_CODE_REASON_PHRASE.n301_moved_permanently.reason_phrase,
2227 302 => STATUS_CODE_REASON_PHRASE.n302_found.reason_phrase,
2228 307 => STATUS_CODE_REASON_PHRASE.n307_temporary_redirect.reason_phrase,
2229 308 => STATUS_CODE_REASON_PHRASE.n308_permanent_redirect.reason_phrase,
2230 _ => "Redirect",
2231 };
2232 (code as i16, phrase.to_string())
2233}
2234
2235impl Application for RedirectAdapter {
2236 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2237 let location = self
2238 .location_template
2239 .replace("$path", &request.request_uri);
2240 use crate::header::Header;
2241 let mut r = Response::new();
2242 r.status_code = self.status;
2243 r.reason_phrase = self.reason.as_ref().clone();
2244 r.headers.push(Header { name: "Location".into(), value: location.into() });
2245 Ok(r)
2246 }
2247}
2248
2249#[derive(Clone)]
2253pub(crate) struct RespondAdapter {
2254 status: i16,
2255 reason: Arc<String>,
2256 body: Arc<Vec<u8>>,
2257 content_type: Arc<String>,
2258}
2259
2260impl RespondAdapter {
2261 pub(crate) fn new(status: u16, body: String, content_type: String) -> Self {
2262 use crate::response::STATUS_CODE_REASON_PHRASE;
2263 let reason = match status {
2264 200 => STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
2265 201 => STATUS_CODE_REASON_PHRASE.n201_created.reason_phrase.to_string(),
2266 204 => STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string(),
2267 400 => STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string(),
2268 401 => STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string(),
2269 403 => STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string(),
2270 404 => STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string(),
2271 500 => STATUS_CODE_REASON_PHRASE.n500_internal_server_error.reason_phrase.to_string(),
2272 _ => "OK".to_string(),
2273 };
2274 RespondAdapter {
2275 status: status as i16,
2276 reason: Arc::new(reason),
2277 body: Arc::new(body.into_bytes()),
2278 content_type: Arc::new(content_type),
2279 }
2280 }
2281}
2282
2283impl Application for RespondAdapter {
2284 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2285 use crate::range::Range;
2286 let mut r = Response::new();
2287 r.status_code = self.status;
2288 r.reason_phrase = self.reason.as_ref().clone();
2289 if !self.body.is_empty() {
2290 r.content_range_list = vec![Range::get_content_range(
2291 self.body.as_ref().clone(),
2292 self.content_type.as_ref().clone(),
2293 )];
2294 }
2295 Ok(r)
2296 }
2297}
2298
2299#[derive(Clone)]
2308pub(crate) struct StaticAdapter {
2309 root: Arc<std::path::PathBuf>,
2310 index: Arc<Vec<String>>,
2311}
2312
2313impl StaticAdapter {
2314 pub(crate) fn new(root: String, index: Vec<String>) -> Self {
2315 let index = if index.is_empty() { vec!["index.html".to_string()] } else { index };
2316 StaticAdapter {
2317 root: Arc::new(std::path::PathBuf::from(root)),
2318 index: Arc::new(index),
2319 }
2320 }
2321
2322 fn resolve(&self, request_uri: &str) -> Option<std::path::PathBuf> {
2326 let raw_path = request_uri.split('?').next().unwrap_or(request_uri);
2327 let decoded = crate::url::URL::percent_decode(raw_path);
2328
2329 if decoded.split('/').any(|segment| segment == "..") {
2330 return None;
2331 }
2332
2333 let relative = decoded.trim_start_matches('/');
2334 Some(self.root.join(relative))
2335 }
2336}
2337
2338impl Application for StaticAdapter {
2339 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2340 let mut response = Response::new();
2341
2342 let not_found = |mut response: Response| {
2343 response.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
2344 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
2345 response
2346 };
2347
2348 let candidate = match self.resolve(&request.request_uri) {
2349 Some(p) => p,
2350 None => {
2351 response.status_code = *STATUS_CODE_REASON_PHRASE.n403_forbidden.status_code;
2352 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string();
2353 return Ok(response);
2354 }
2355 };
2356
2357 let mut file_path = candidate;
2358 if file_path.is_dir() {
2359 let indexed = self
2360 .index
2361 .iter()
2362 .map(|name| file_path.join(name))
2363 .find(|p| p.is_file());
2364
2365 file_path = match indexed {
2366 Some(p) => p,
2367 None => return Ok(not_found(response)),
2368 };
2369 }
2370
2371 if !file_path.is_file() {
2372 return Ok(not_found(response));
2373 }
2374
2375 if let (Ok(root_canon), Ok(file_canon)) =
2378 (self.root.canonicalize(), file_path.canonicalize())
2379 {
2380 if !file_canon.starts_with(&root_canon) {
2381 return Ok(not_found(response));
2382 }
2383 }
2384
2385 let path_str = match file_path.to_str() {
2386 Some(s) => s,
2387 None => return Ok(not_found(response)),
2388 };
2389
2390 match crate::range::Range::get_content_range_of_a_file(path_str) {
2391 Ok(content_range) => {
2392 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
2393 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
2394 response.content_range_list = vec![content_range];
2395 Ok(response)
2396 }
2397 Err(_) => Ok(not_found(response)),
2398 }
2399 }
2400}
2401
2402pub(crate) struct PerRouteRateLimit(pub(crate) Arc<crate::rate_limit::RateLimiter>);
2406
2407impl crate::middleware::Middleware for PerRouteRateLimit {
2408 fn handle(
2409 &self,
2410 request: &Request,
2411 conn: &ConnectionInfo,
2412 next: &dyn Application,
2413 ) -> Result<Response, String> {
2414 use crate::error::{AppError, IntoResponse};
2415 if self.0.check(&conn.client.ip) {
2416 next.execute(request, conn)
2417 } else {
2418 let mut response = AppError::TooManyRequests.into_response();
2419 let retry_after = self.0.retry_after(&conn.client.ip);
2420 response.headers.push(Header {
2421 name: Header::_RETRY_AFTER.into(),
2422 value: retry_after.to_string().into(),
2423 });
2424 Ok(response)
2425 }
2426 }
2427}
2428
2429pub(crate) struct PerRouteMaxBodySize(pub(crate) u64);
2444
2445impl crate::middleware::Middleware for PerRouteMaxBodySize {
2446 fn handle(
2447 &self,
2448 request: &Request,
2449 conn: &ConnectionInfo,
2450 next: &dyn Application,
2451 ) -> Result<Response, String> {
2452 use crate::error::{AppError, IntoResponse};
2453 if request.body.len() as u64 > self.0 {
2454 Ok(AppError::PayloadTooLarge(format!(
2455 "request body of {} bytes exceeds this route's {}-byte limit",
2456 request.body.len(),
2457 self.0,
2458 ))
2459 .into_response())
2460 } else {
2461 next.execute(request, conn)
2462 }
2463 }
2464}
2465
2466pub(crate) struct BearerAuthMiddleware {
2470 pub(crate) token: Arc<String>,
2471}
2472
2473impl crate::middleware::Middleware for BearerAuthMiddleware {
2474 fn handle(
2475 &self,
2476 request: &Request,
2477 conn: &ConnectionInfo,
2478 next: &dyn Application,
2479 ) -> Result<Response, String> {
2480 use crate::error::{AppError, IntoResponse};
2481 let expected = format!("Bearer {}", self.token);
2482 let authorized = request
2483 .headers
2484 .iter()
2485 .any(|h| h.name.eq_ignore_ascii_case("authorization") && constant_time_eq(h.value.as_bytes(), expected.as_bytes()));
2486 if authorized {
2487 next.execute(request, conn)
2488 } else {
2489 Ok(AppError::Unauthorized.into_response())
2490 }
2491 }
2492}
2493
2494pub(crate) use crate::core::constant_time_eq;
2495
2496pub(crate) struct AuthMisconfigured;
2499
2500pub(crate) fn cache_max_bytes(max_size: &str) -> Option<u64> {
2502 if max_size.trim().is_empty() {
2503 Some(256 * 1024 * 1024)
2504 } else {
2505 crate::log::access_sink::parse_size(max_size.trim()).filter(|n| *n > 0)
2506 }
2507}
2508
2509pub(crate) struct PluginMisconfigured;
2512
2513impl crate::middleware::Middleware for PluginMisconfigured {
2514 fn handle(&self, _request: &Request, _conn: &ConnectionInfo, _next: &dyn Application) -> Result<Response, String> {
2515 Ok(crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "this route's plugin is misconfigured"))
2516 }
2517}
2518
2519impl crate::middleware::Middleware for AuthMisconfigured {
2520 fn handle(
2521 &self,
2522 _request: &Request,
2523 _conn: &ConnectionInfo,
2524 _next: &dyn Application,
2525 ) -> Result<Response, String> {
2526 Ok(crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "authentication for this route is misconfigured"))
2527 }
2528}
2529
2530pub(crate) fn arc_app<A: Application + Send + Sync + 'static>(
2534 a: A,
2535) -> Arc<dyn Application + Send + Sync> {
2536 Arc::new(a)
2537}
2538
2539pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
2544 builder::build_from_file()
2545}