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