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 if crate::traffic::enabled() {
1345 crate::traffic::configure_sites(&toml);
1346 }
1347 for w in &warnings {
1348 eprintln!("[proxy_config] Warning: {w}");
1349 }
1350 println!("[proxy_config] reloaded {path}: {routes} route{}", if routes == 1 { "" } else { "s" });
1351 }
1352 Err(report) => {
1353 for e in &report.errors {
1354 eprintln!("[proxy_config] reload ERROR: {e}");
1355 crate::config_reload::report_error(e.clone());
1356 }
1357 eprintln!("[proxy_config] {path} has errors — keeping the running routes");
1358 }
1359 }
1360 }
1361}
1362
1363fn normalized(request: &Request) -> std::borrow::Cow<'_, Request> {
1366 match normalize_request_target(&request.request_uri) {
1367 std::borrow::Cow::Borrowed(_) => std::borrow::Cow::Borrowed(request),
1368 std::borrow::Cow::Owned(target) => {
1369 let mut owned = request.clone();
1370 owned.request_uri = target;
1371 std::borrow::Cow::Owned(owned)
1372 }
1373 }
1374}
1375
1376#[doc(hidden)]
1384pub fn normalize_request_target(target: &str) -> std::borrow::Cow<'_, str> {
1385 let (path, query) = match target.find('?') {
1386 Some(i) => (&target[..i], &target[i..]),
1387 None => (target, ""),
1388 };
1389 if !path.starts_with('/') || !(path.contains('%') || path.contains("//") || path.contains("/.")) {
1390 return std::borrow::Cow::Borrowed(target);
1391 }
1392 let bytes = path.as_bytes();
1393 let mut decoded = String::with_capacity(path.len());
1394 let mut i = 0;
1395 while i < bytes.len() {
1396 let hex = |b: u8| (b as char).to_digit(16);
1397 if bytes[i] == b'%' && i + 2 < bytes.len() {
1398 if let (Some(h), Some(l)) = (hex(bytes[i + 1]), hex(bytes[i + 2])) {
1399 let value = (h * 16 + l) as u8;
1400 if value.is_ascii_alphanumeric() || matches!(value, b'-' | b'.' | b'_' | b'~') {
1401 decoded.push(value as char);
1402 } else {
1403 decoded.push('%');
1404 decoded.push(bytes[i + 1].to_ascii_uppercase() as char);
1405 decoded.push(bytes[i + 2].to_ascii_uppercase() as char);
1406 }
1407 i += 3;
1408 continue;
1409 }
1410 }
1411 if bytes[i] == b'%' {
1415 decoded.push_str("%25");
1416 i += 1;
1417 continue;
1418 }
1419 let ch = path[i..].chars().next().unwrap_or('\u{FFFD}');
1421 decoded.push(ch);
1422 i += ch.len_utf8().max(1);
1423 }
1424 let mut segments: Vec<&str> = Vec::new();
1425 let parts: Vec<&str> = decoded.split('/').collect();
1426 let last = parts.len().saturating_sub(1);
1427 let mut trailing_slash = false;
1428 for (n, segment) in parts.iter().enumerate() {
1429 match *segment {
1430 "" => trailing_slash = n == last,
1431 "." => trailing_slash = n == last,
1432 ".." => {
1433 segments.pop();
1434 trailing_slash = n == last;
1435 }
1436 s => {
1437 segments.push(s);
1438 trailing_slash = false;
1439 }
1440 }
1441 }
1442 let mut out = String::with_capacity(decoded.len() + query.len());
1443 out.push('/');
1444 out.push_str(&segments.join("/"));
1445 if trailing_slash && !segments.is_empty() {
1446 out.push('/');
1447 }
1448 out.push_str(query);
1449 if out == target {
1450 std::borrow::Cow::Borrowed(target)
1451 } else {
1452 std::borrow::Cow::Owned(out)
1453 }
1454}
1455
1456impl Application for ConfigDrivenApp {
1457 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1458 let request = normalized(request);
1459 let generation = self.current();
1460 match &generation.entry {
1461 Some(entry) => entry.execute(&request, conn),
1462 None => generation.dispatch.execute(&request, conn),
1463 }
1464 }
1465
1466 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1469 let request = normalized(request);
1470 let generation = self.current();
1471 match &generation.entry {
1472 Some(entry) => entry.streams_body(&request, conn),
1473 None => generation.dispatch.streams_body(&request, conn),
1474 }
1475 }
1476
1477 fn runs_inline(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1481 let request = normalized(request);
1482 let generation = self.current();
1483 generation.entry.is_none()
1484 && !generation.dispatch.routes.iter().any(|r| r.matcher.matches(&request, conn))
1485 && App::is_inline_request(&request)
1486 }
1487
1488 #[cfg(feature = "http2")]
1492 fn execute_async<'a>(&'a self, request: &'a Request, conn: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
1493 let generation = self.current();
1494 if generation.entry.is_some() || crate::proxy::upgrade::is_upgrade(request) {
1496 return None;
1497 }
1498 let request = normalized(request);
1499 let route = generation.dispatch.routes.iter().find(|r| r.matcher.matches(&request, conn))?;
1500 let proxy = Arc::clone(route.async_proxy.as_ref()?);
1501 if let Some(preflight) = route_cors::preflight(&request, &crate::server_config::ServerConfig::current()) {
1502 return Some(Box::pin(std::future::ready(Ok(preflight))));
1503 }
1504 Some(Box::pin(async move {
1505 let response = proxy.execute_async(&request, conn).await;
1506 response.map(|r| route_cors::add(&request, r, &crate::server_config::ServerConfig::current()))
1507 }))
1508 }
1509}
1510
1511mod route_cors {
1516 use crate::header::Header;
1517 use crate::request::{Request, METHOD};
1518 use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
1519 use crate::server_config::ServerConfig;
1520
1521 fn headers(request: &Request, config: &ServerConfig) -> Vec<Header> {
1522 if request.header(Header::_ORIGIN).is_none() {
1523 return Vec::new();
1524 }
1525 rws_server::cors::Cors::get_headers_from_config(request, config)
1526 }
1527
1528 pub(super) fn preflight(request: &Request, config: &ServerConfig) -> Option<Response> {
1532 if request.method != METHOD.options || request.header(Header::_ACCESS_CONTROL_REQUEST_METHOD).is_none() {
1533 return None;
1534 }
1535 let headers = headers(request, config);
1536 if headers.is_empty() {
1537 return None;
1538 }
1539 let mut response = Response::default();
1540 response.status_code = *STATUS_CODE_REASON_PHRASE.n204_no_content.status_code;
1541 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string();
1542 response.headers = headers;
1543 response.headers.push(Header { name: Header::_VARY.into(), value: "Origin".into() });
1544 Some(response)
1545 }
1546
1547 pub(super) fn add(request: &Request, mut response: Response, config: &ServerConfig) -> Response {
1548 if response.headers.iter().any(|h| h.name.eq_ignore_ascii_case(Header::_ACCESS_CONTROL_ALLOW_ORIGIN)) {
1549 return response;
1550 }
1551 let headers = headers(request, config);
1552 if headers.is_empty() {
1553 return response;
1554 }
1555 response.headers.extend(headers);
1556 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() == "*"));
1557 if !varies {
1558 response.headers.push(Header { name: Header::_VARY.into(), value: "Origin".into() });
1559 }
1560 response
1561 }
1562}
1563
1564#[cfg(feature = "http2")]
1570#[doc(hidden)]
1571pub struct H2Route {
1572 live: Arc<RwLock<Vec<String>>>,
1573 tls: bool,
1574 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1575 connect_timeout: std::time::Duration,
1576 read_timeout: std::time::Duration,
1577 counter: std::sync::atomic::AtomicUsize,
1578}
1579
1580#[cfg(feature = "http2")]
1581impl H2Route {
1582 #[doc(hidden)]
1583 pub fn new(
1584 live: Arc<RwLock<Vec<String>>>,
1585 tls: bool,
1586 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1587 connect_timeout_ms: u64,
1588 read_timeout_ms: u64,
1589 ) -> H2Route {
1590 H2Route {
1591 live,
1592 tls,
1593 trust,
1594 connect_timeout: std::time::Duration::from_millis(connect_timeout_ms),
1595 read_timeout: std::time::Duration::from_millis(read_timeout_ms),
1596 counter: std::sync::atomic::AtomicUsize::new(0),
1597 }
1598 }
1599}
1600
1601#[cfg(feature = "http2")]
1602impl Application for H2Route {
1603 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1604 use crate::middleware::Middleware;
1605 crate::proxy::GrpcWebLayer::new().handle(request, conn, &H2Forward(self))
1606 }
1607
1608 fn streams_body(&self, _request: &Request, _conn: &ConnectionInfo) -> bool {
1609 true
1610 }
1611}
1612
1613#[cfg(feature = "http2")]
1615struct H2Forward<'a>(&'a H2Route);
1616
1617#[cfg(feature = "http2")]
1618impl Application for H2Forward<'_> {
1619 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1620 let route = self.0;
1621 let backends = route.live.read().unwrap_or_else(|e| e.into_inner()).clone();
1622 if backends.is_empty() {
1623 let mut response = crate::proxy::bad_gateway();
1624 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::DestinationUnavailable, None, None);
1625 return Ok(response);
1626 }
1627 let attempts = if matches!(request.body, crate::request::Body::Stream(_)) { 1 } else { backends.len() };
1629 let start = route.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1630 let mut failure: Option<(String, String)> = None;
1631 for attempt in 0..attempts {
1632 let url = &backends[(start + attempt) % backends.len()];
1633 let result = crate::proxy::forward_h2(
1634 request, crate::proxy::Peer::of(conn), url, route.tls, route.trust.as_deref(), route.connect_timeout, route.read_timeout,
1635 );
1636 match result {
1637 Ok(mut response) => {
1638 crate::proxy::status::received(&mut response, Some(url));
1639 return Ok(response);
1640 }
1641 Err(e) => {
1642 eprintln!("grpc route: {e}");
1643 failure = Some((e, url.clone()));
1644 }
1645 }
1646 }
1647 let mut response = crate::proxy::bad_gateway();
1648 if let Some((e, url)) = &failure {
1649 crate::proxy::status::failed_with(&mut response, e, Some(url));
1650 }
1651 Ok(response)
1652 }
1653}
1654
1655#[derive(Clone, Copy)]
1660#[doc(hidden)]
1661pub struct NullApp;
1662
1663impl Application for NullApp {
1664 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1665 let mut r = Response::new();
1666 r.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1667 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1668 Ok(r)
1669 }
1670}
1671
1672use std::collections::HashMap;
1675use std::collections::hash_map::DefaultHasher;
1676use std::hash::{Hash, Hasher};
1677use std::sync::atomic::AtomicUsize;
1678use std::time::{Duration, SystemTime, UNIX_EPOCH};
1679
1680#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1684#[doc(hidden)]
1685pub enum LoadBalanceStrategy {
1686 RoundRobin,
1687 Random,
1688 IpHash,
1689 LeastConnections,
1690}
1691
1692impl LoadBalanceStrategy {
1693 fn parse(s: &str) -> Self {
1694 match s {
1695 "random" => LoadBalanceStrategy::Random,
1696 "ip_hash" => LoadBalanceStrategy::IpHash,
1697 "least_connections" => LoadBalanceStrategy::LeastConnections,
1698 _ => LoadBalanceStrategy::RoundRobin,
1699 }
1700 }
1701}
1702
1703#[derive(Clone)]
1711#[doc(hidden)]
1712pub struct DynamicProxy {
1713 live: Arc<RwLock<Vec<String>>>,
1714 counter: Arc<AtomicUsize>,
1715 connect_timeout: Duration,
1716 read_timeout: Duration,
1717 strip_prefix: Option<Arc<String>>,
1718 add_prefix: Option<Arc<String>>,
1719 tls: bool,
1720 strategy: LoadBalanceStrategy,
1721 connections: Arc<RwLock<HashMap<String, Arc<AtomicUsize>>>>,
1722 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1724 upstream: Arc<String>,
1726 policy: Option<Arc<upstream::UpstreamPolicy>>,
1728 mirror: Option<crate::proxy::mirror::Mirror>,
1731 early_hints: Option<Arc<Vec<String>>>,
1733 #[cfg(feature = "http3")]
1736 h3: Option<Arc<std::sync::Mutex<HashMap<String, std::time::Instant>>>>,
1737 upstream_host: UpstreamHost,
1739}
1740
1741impl DynamicProxy {
1742 #[doc(hidden)]
1744 pub fn with_upstream_host(mut self, host: UpstreamHost) -> Self {
1745 self.upstream_host = host;
1746 self
1747 }
1748
1749 fn host_override<'a>(&'a self, request: &'a Request) -> Option<&'a str> {
1751 match &self.upstream_host {
1752 UpstreamHost::Backend => None,
1753 UpstreamHost::Client => request.header(Header::_HOST).map(|h| h.value.as_ref()).filter(|v| !v.is_empty()),
1754 UpstreamHost::Fixed(host) => Some(host),
1755 }
1756 }
1757
1758 #[doc(hidden)]
1760 pub fn with_trust(mut self, trust: Option<Arc<crate::proxy::UpstreamTls>>) -> Self {
1761 self.trust = trust;
1762 self
1763 }
1764
1765 #[doc(hidden)]
1770 pub fn with_early_hints(mut self, links: &[String]) -> Self {
1771 self.early_hints = (!links.is_empty()).then(|| Arc::new(links.to_vec()));
1772 self
1773 }
1774
1775 #[cfg_attr(not(feature = "http3"), allow(unused_mut))]
1779 #[doc(hidden)]
1780 pub fn with_h3(mut self, enabled: bool) -> Self {
1781 #[cfg(feature = "http3")]
1782 {
1783 self.h3 = (enabled && self.tls).then(Default::default);
1784 }
1785 #[cfg(not(feature = "http3"))]
1786 let _ = enabled;
1787 self
1788 }
1789
1790 #[cfg(feature = "http3")]
1793 fn h3_target(&self, backend: &str) -> Option<Arc<rustls::ClientConfig>> {
1794 let tcp_until = self.h3.as_ref()?;
1795 let mut tcp_until = tcp_until.lock().unwrap_or_else(|e| e.into_inner());
1796 if let Some(until) = tcp_until.get(backend) {
1797 if std::time::Instant::now() < *until {
1798 return None;
1799 }
1800 tcp_until.remove(backend);
1801 }
1802 Some(match &self.trust {
1803 Some(trust) => trust.client_config(),
1804 None => crate::proxy::tls_client_config(),
1805 })
1806 }
1807
1808 #[cfg(feature = "http3")]
1810 fn fall_back_to_tcp(&self, backend: &str, error: &str) {
1811 if let Some(tcp_until) = &self.h3 {
1812 tcp_until.lock().unwrap_or_else(|e| e.into_inner()).insert(backend.to_string(), std::time::Instant::now() + H3_FALLBACK);
1813 }
1814 upstream::observe_h3_fallback(&self.upstream);
1815 eprintln!("upstream {}: HTTP/3 to {backend} failed ({error}); using TCP for {} s", self.upstream, H3_FALLBACK.as_secs());
1816 }
1817
1818 #[doc(hidden)]
1820 pub fn with_mirror(mut self, mirror: Option<crate::proxy::mirror::Mirror>) -> Self {
1821 self.mirror = mirror;
1822 self
1823 }
1824
1825 #[doc(hidden)]
1826 pub fn with_policy(mut self, upstream: &str, policy: Option<Arc<upstream::UpstreamPolicy>>) -> Self {
1827 self.upstream = Arc::new(upstream.to_string());
1828 self.policy = policy;
1829 self
1830 }
1831
1832 #[doc(hidden)]
1833 pub fn new(
1834 live: Arc<RwLock<Vec<String>>>,
1835 connect_timeout_ms: u64,
1836 read_timeout_ms: u64,
1837 strip_prefix: Option<String>,
1838 add_prefix: Option<String>,
1839 tls: bool,
1840 strategy: String,
1841 ) -> Self {
1842 DynamicProxy {
1843 live,
1844 counter: Arc::new(AtomicUsize::new(0)),
1845 connect_timeout: Duration::from_millis(connect_timeout_ms),
1846 read_timeout: Duration::from_millis(read_timeout_ms),
1847 strip_prefix: strip_prefix.map(Arc::new),
1848 add_prefix: add_prefix.map(Arc::new),
1849 tls,
1850 strategy: LoadBalanceStrategy::parse(&strategy),
1851 connections: Arc::new(RwLock::new(HashMap::new())),
1852 trust: None,
1853 upstream: Arc::new(String::new()),
1854 policy: None,
1855 mirror: None,
1856 early_hints: None,
1857 #[cfg(feature = "http3")]
1858 h3: None,
1859 upstream_host: UpstreamHost::Backend,
1860 }
1861 }
1862
1863 fn pick(&self, candidates: &[String], client_ip: &str) -> String {
1865 let policy = self.policy.as_deref().filter(|p| p.weighted());
1866 let idx = match self.strategy {
1867 LoadBalanceStrategy::RoundRobin => match policy {
1868 Some(policy) => return policy.weighted_round_robin(candidates).unwrap_or_else(|| candidates[0].clone()),
1869 None => self.counter.fetch_add(1, Ordering::Relaxed) % candidates.len(),
1870 },
1871 LoadBalanceStrategy::Random => {
1872 let nanos = SystemTime::now()
1873 .duration_since(UNIX_EPOCH)
1874 .map(|d| d.subsec_nanos())
1875 .unwrap_or(0) as usize;
1876 let salt = self.counter.fetch_add(1, Ordering::Relaxed);
1877 let roll = nanos.wrapping_add(salt.wrapping_mul(2_654_435_761));
1878 match policy {
1879 Some(policy) => policy.weighted_index(candidates, roll),
1880 None => roll % candidates.len(),
1881 }
1882 }
1883 LoadBalanceStrategy::IpHash => {
1884 let mut hasher = DefaultHasher::new();
1885 client_ip.hash(&mut hasher);
1886 (hasher.finish() as usize) % candidates.len()
1887 }
1888 LoadBalanceStrategy::LeastConnections => {
1889 let connections = self.connections.read().unwrap();
1890 let load = |backend: &String| connections.get(backend).map(|c| c.load(Ordering::Relaxed)).unwrap_or(0) as u64;
1891 let weight = |backend: &String| self.policy.as_deref().map_or(1, |p| p.weight(backend)) as u64;
1892 candidates
1894 .iter()
1895 .enumerate()
1896 .min_by(|(_, a), (_, b)| (load(a) * weight(b)).cmp(&(load(b) * weight(a))))
1897 .map(|(i, _)| i)
1898 .unwrap_or(0)
1899 }
1900 };
1901 candidates[idx].clone()
1902 }
1903
1904 fn next_backend(&self, request: &Request, client_ip: &str, tried: &[String]) -> Result<String, bool> {
1909 let mut candidates: Vec<String> = self.live.read().unwrap().iter().filter(|b| !tried.contains(b)).cloned().collect();
1910 if !candidates.is_empty() {
1911 candidates.retain(|b| !upstream::is_drained(&self.upstream, b));
1912 if candidates.is_empty() {
1913 return Err(true);
1915 }
1916 }
1917 let Some(policy) = self.policy.as_deref() else {
1918 return if candidates.is_empty() { Err(false) } else { Ok(self.pick(&candidates, client_ip)) };
1919 };
1920 if let Some(sticky) = &policy.sticky {
1921 if let Some(id) = upstream::cookie_value(request, &sticky.cookie) {
1922 if let Some(backend) = candidates.iter().find(|b| upstream::backend_id(b) == id).cloned() {
1923 if policy.admits(&backend) {
1924 return Ok(backend);
1925 }
1926 candidates.retain(|b| *b != backend);
1927 }
1928 }
1929 }
1930 let ejected = policy.any_open(&candidates);
1931 while !candidates.is_empty() {
1932 let backend = self.pick(&candidates, client_ip);
1933 if policy.admits(&backend) {
1934 return Ok(backend);
1935 }
1936 candidates.retain(|b| *b != backend);
1937 }
1938 Err(ejected)
1939 }
1940
1941 fn connection_counter(&self, backend: &str) -> Arc<AtomicUsize> {
1944 if let Some(counter) = self.connections.read().unwrap().get(backend) {
1945 return Arc::clone(counter);
1946 }
1947 let mut connections = self.connections.write().unwrap();
1948 Arc::clone(
1949 connections
1950 .entry(backend.to_string())
1951 .or_insert_with(|| Arc::new(AtomicUsize::new(0))),
1952 )
1953 }
1954}
1955
1956struct ConnectionGuard {
1959 counter: Arc<AtomicUsize>,
1960}
1961
1962impl Drop for ConnectionGuard {
1963 fn drop(&mut self) {
1964 self.counter.fetch_sub(1, Ordering::Relaxed);
1965 }
1966}
1967
1968struct Prepared<'a> {
1971 backend: String,
1972 host: String,
1973 port: u16,
1974 request: std::borrow::Cow<'a, Request>,
1975 _guard: Option<ConnectionGuard>,
1976}
1977
1978enum Attempt {
1980 Done(Response),
1982 Failed(Option<Response>),
1985}
1986
1987impl DynamicProxy {
1988 fn forward_upgrade(&self, request: &Request, conn: &ConnectionInfo) -> Response {
1991 let prepared = match self.prepare(request, conn, &[]) {
1992 Ok(p) => p,
1993 Err(response) => return response,
1994 };
1995 if self.tls || prepared.backend.starts_with("https://") || prepared.backend.starts_with("wss://") {
1996 eprintln!("[proxy] upgrade to {}: TLS backends can't take upgraded connections yet", prepared.backend);
1997 return bad_gateway();
1998 }
1999 let default_host = if prepared.port == 80 { prepared.host.clone() } else { format!("{}:{}", prepared.host, prepared.port) };
2000 let host_header = self.host_override(request).map(str::to_string).unwrap_or(default_host);
2001 match crate::proxy::upgrade::forward(&prepared.request, crate::proxy::Peer::of(conn), &prepared.host, prepared.port, &host_header, self.read_timeout) {
2002 Ok(response) => response,
2003 Err(e) => {
2004 eprintln!("[proxy] upgrade to {}: {e}", prepared.backend);
2005 let mut response = bad_gateway();
2006 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ConnectionRefused, Some(&prepared.backend), None);
2007 response
2008 }
2009 }
2010 }
2011
2012 fn prepare<'a>(&self, request: &'a Request, conn: &ConnectionInfo, tried: &[String]) -> Result<Prepared<'a>, Response> {
2017 let backend = self.next_backend(request, &crate::ip_filter::client_ip(request, &conn.client.ip), tried).map_err(|ejected| {
2018 let mut response = if ejected { service_unavailable() } else { bad_gateway() };
2019 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::DestinationUnavailable, None, None);
2020 response
2021 })?;
2022
2023 let guard = if self.strategy == LoadBalanceStrategy::LeastConnections {
2024 let counter = self.connection_counter(&backend);
2025 counter.fetch_add(1, Ordering::Relaxed);
2026 Some(ConnectionGuard { counter })
2027 } else {
2028 None
2029 };
2030
2031 let (host, port, _) = crate::proxy_config::health::parse_backend_url(&backend).ok_or_else(|| {
2032 let mut response = bad_gateway();
2033 crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyConfigurationError, Some(&backend), None);
2034 response
2035 })?;
2036
2037 let request = if self.strip_prefix.is_some() || self.add_prefix.is_some() {
2039 let mut req_clone = request.clone();
2040 if let Some(ref sp) = self.strip_prefix {
2041 if let Some(stripped) = req_clone.request_uri.strip_prefix(sp.as_str()) {
2042 req_clone.request_uri = if stripped.is_empty() || !stripped.starts_with('/') {
2043 format!("/{}", stripped)
2044 } else {
2045 stripped.to_string()
2046 };
2047 }
2048 }
2049 if let Some(ref ap) = self.add_prefix {
2050 req_clone.request_uri = format!("{}{}", ap, req_clone.request_uri);
2051 }
2052 std::borrow::Cow::Owned(req_clone)
2053 } else {
2054 std::borrow::Cow::Borrowed(request)
2055 };
2056
2057 Ok(Prepared { backend, host, port, request, _guard: guard })
2058 }
2059
2060 fn settle(&self, backend: &str, result: Result<Response, String>, started: std::time::Instant, failure: &mut Option<(String, String)>) -> Attempt {
2062 let result = match result {
2063 Ok(mut response) => {
2064 crate::proxy::status::received(&mut response, Some(backend));
2065 Ok(response)
2066 }
2067 Err(e) => {
2068 *failure = Some((e, backend.to_string()));
2069 Err(())
2070 }
2071 };
2072 let status = result.as_ref().ok().map(|r| r.status_code);
2073 upstream::observe(&self.upstream, backend, status, started);
2074 let failed = status.is_none_or(|s| matches!(s, 502..=504));
2075 if let Some(policy) = &self.policy {
2076 policy.record(backend, failed);
2077 }
2078 match result {
2079 Ok(response) if self.policy.as_deref().is_some_and(|p| p.retries_status(response.status_code)) => Attempt::Failed(Some(response)),
2080 Ok(response) => Attempt::Done(response),
2081 Err(()) => Attempt::Failed(None),
2082 }
2083 }
2084
2085 fn attempts(&self, request: &Request) -> u32 {
2087 match self.policy.as_deref().and_then(|p| p.retry.as_ref()) {
2088 Some(retry) if upstream::replayable(request) => 1 + retry.attempts,
2089 _ => 1,
2090 }
2091 }
2092
2093 fn stick(&self, request: &Request, backend: &str, response: &mut Response) {
2095 let Some(sticky) = self.policy.as_deref().and_then(|p| p.sticky.as_ref()) else { return };
2096 let id = upstream::backend_id(backend);
2097 if upstream::cookie_value(request, &sticky.cookie).as_deref() == Some(id.as_str()) {
2098 return;
2099 }
2100 let max_age = sticky.max_age_secs.map(|s| format!("; Max-Age={s}")).unwrap_or_default();
2101 response.headers.push(crate::header::Header {
2102 name: "Set-Cookie".into(),
2103 value: format!("{}={id}; Path=/; HttpOnly; SameSite=Lax{max_age}", sticky.cookie).into(),
2104 });
2105 }
2106
2107 #[cfg(feature = "http2")]
2110 #[doc(hidden)]
2111 pub async fn execute_async(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
2112 if let Some(mirror) = &self.mirror {
2113 mirror.send(request, conn);
2114 }
2115 if let Some(links) = &self.early_hints {
2116 crate::early_hints::send_from(crate::early_hints::Source::Configured, links);
2117 }
2118 let mut tried: Vec<String> = Vec::new();
2119 let mut last: Option<Response> = None;
2120 let mut failure: Option<(String, String)> = None;
2121 for attempt in 0..self.attempts(request) {
2122 let prepared = match self.prepare(request, conn, &tried) {
2123 Ok(p) => p,
2124 Err(response) => return Ok(last.unwrap_or(response)),
2125 };
2126 if attempt > 0 {
2127 upstream::observe_retry(&self.upstream);
2128 }
2129 let tls = self.tls.then(|| match &self.trust {
2130 Some(trust) => trust.async_tls(),
2131 None => crate::proxy::async_upstream::AsyncTls {
2132 config: crate::proxy::tls_client_config(),
2133 pool: crate::proxy::async_upstream::default_tls_pool(),
2134 },
2135 });
2136 let started = std::time::Instant::now();
2137 #[cfg(feature = "http3")]
2138 if let Some(config) = self.h3_target(&prepared.backend) {
2139 let target = crate::proxy::h3_upstream::Target {
2140 host: &prepared.host,
2141 port: prepared.port,
2142 authority: self.host_override(request),
2143 tls: &config,
2144 connect_timeout: self.connect_timeout,
2145 read_timeout: self.read_timeout,
2146 };
2147 match crate::proxy::h3_upstream::forward_async(&prepared.request, crate::proxy::Peer::of(conn), &target).await {
2148 Err(crate::proxy::h3_upstream::H3Error::Connect(e)) => self.fall_back_to_tcp(&prepared.backend, &e),
2149 result => {
2150 match self.settle(&prepared.backend, result.map_err(|e| e.to_string()), started, &mut failure) {
2151 Attempt::Done(mut response) => {
2152 self.stick(request, &prepared.backend, &mut response);
2153 return Ok(response);
2154 }
2155 Attempt::Failed(response) => last = response,
2156 }
2157 tried.push(prepared.backend.clone());
2158 continue;
2159 }
2160 }
2161 }
2162 let result = crate::proxy::async_upstream::forward_as(
2163 &prepared.request,
2164 crate::proxy::Peer::of(conn),
2165 &prepared.host,
2166 prepared.port,
2167 self.host_override(request).unwrap_or(&prepared.host),
2168 tls,
2169 self.connect_timeout,
2170 self.read_timeout,
2171 )
2172 .await;
2173 match self.settle(&prepared.backend, result, started, &mut failure) {
2174 Attempt::Done(mut response) => {
2175 self.stick(request, &prepared.backend, &mut response);
2176 return Ok(response);
2177 }
2178 Attempt::Failed(response) => last = response,
2179 }
2180 tried.push(prepared.backend.clone());
2181 }
2182 Ok(last.unwrap_or_else(|| {
2183 let mut response = bad_gateway();
2184 match &failure {
2185 Some((e, backend)) => crate::proxy::status::failed_with(&mut response, e, Some(backend)),
2186 None => crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyInternalError, None, None),
2187 }
2188 response
2189 }))
2190 }
2191
2192 #[doc(hidden)]
2194 pub fn supports_async(&self) -> bool {
2195 cfg!(feature = "http2")
2196 }
2197}
2198
2199impl Application for DynamicProxy {
2200 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
2201 if crate::proxy::upgrade::is_upgrade(request) {
2204 return Ok(self.forward_upgrade(request, conn));
2205 }
2206 if let Some(mirror) = &self.mirror {
2207 mirror.send(request, conn);
2208 }
2209 if let Some(links) = &self.early_hints {
2210 crate::early_hints::send_from(crate::early_hints::Source::Configured, links);
2211 }
2212 let mut tried: Vec<String> = Vec::new();
2213 let mut last: Option<Response> = None;
2214 let mut failure: Option<(String, String)> = None;
2215 for attempt in 0..self.attempts(request) {
2216 let prepared = match self.prepare(request, conn, &tried) {
2217 Ok(p) => p,
2218 Err(response) => return Ok(last.unwrap_or(response)),
2219 };
2220 if attempt > 0 {
2221 upstream::observe_retry(&self.upstream);
2222 }
2223 let started = std::time::Instant::now();
2226 #[cfg(feature = "http3")]
2227 if let Some(config) = self.h3_target(&prepared.backend) {
2228 let target = crate::proxy::h3_upstream::Target {
2229 host: &prepared.host,
2230 port: prepared.port,
2231 authority: self.host_override(request),
2232 tls: &config,
2233 connect_timeout: self.connect_timeout,
2234 read_timeout: self.read_timeout,
2235 };
2236 match crate::proxy::h3_upstream::forward(&prepared.request, crate::proxy::Peer::of(conn), &target) {
2237 Err(crate::proxy::h3_upstream::H3Error::Connect(e)) => self.fall_back_to_tcp(&prepared.backend, &e),
2238 result => {
2239 match self.settle(&prepared.backend, result.map_err(|e| e.to_string()), started, &mut failure) {
2240 Attempt::Done(mut response) => {
2241 self.stick(request, &prepared.backend, &mut response);
2242 return Ok(response);
2243 }
2244 Attempt::Failed(response) => last = response,
2245 }
2246 tried.push(prepared.backend.clone());
2247 continue;
2248 }
2249 }
2250 }
2251 let result = crate::proxy::forward_upstream_as(
2252 crate::proxy::shared_pool(),
2253 &prepared.request,
2254 crate::proxy::Peer::of(conn),
2255 &prepared.host,
2256 prepared.port,
2257 self.host_override(request).unwrap_or(&prepared.host),
2258 self.tls,
2259 self.trust.as_deref(),
2260 self.connect_timeout,
2261 self.read_timeout,
2262 );
2263 match self.settle(&prepared.backend, result, started, &mut failure) {
2264 Attempt::Done(mut response) => {
2265 self.stick(request, &prepared.backend, &mut response);
2266 return Ok(response);
2267 }
2268 Attempt::Failed(response) => last = response,
2269 }
2270 tried.push(prepared.backend.clone());
2271 }
2272 Ok(last.unwrap_or_else(|| {
2273 let mut response = bad_gateway();
2274 match &failure {
2275 Some((e, backend)) => crate::proxy::status::failed_with(&mut response, e, Some(backend)),
2276 None => crate::proxy::status::failed(&mut response, crate::proxy::status::ProxyError::ProxyInternalError, None, None),
2277 }
2278 response
2279 }))
2280 }
2281}
2282
2283#[cfg(feature = "http3")]
2285#[doc(hidden)]
2286pub const H3_FALLBACK: Duration = Duration::from_secs(300);
2287
2288fn service_unavailable() -> Response {
2289 let mut r = crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "every backend is out of rotation");
2290 r.headers.push(crate::header::Header { name: "Retry-After".into(), value: "5".into() });
2291 r
2292}
2293
2294fn bad_gateway() -> Response {
2295 crate::error::problem(STATUS_CODE_REASON_PHRASE.n502_bad_gateway, "")
2296}
2297
2298#[derive(Clone)]
2304#[doc(hidden)]
2305pub struct RedirectAdapter {
2306 location_template: Arc<String>,
2307 status: i16,
2308 reason: Arc<String>,
2309}
2310
2311impl RedirectAdapter {
2312 #[doc(hidden)]
2313 pub fn new(location: String, status: u16) -> Self {
2314 let (code, reason) = redirect_status(status);
2315 RedirectAdapter {
2316 location_template: Arc::new(location),
2317 status: code,
2318 reason: Arc::new(reason),
2319 }
2320 }
2321}
2322
2323fn redirect_status(code: u16) -> (i16, String) {
2324 let phrase = match code {
2325 301 => STATUS_CODE_REASON_PHRASE.n301_moved_permanently.reason_phrase,
2326 302 => STATUS_CODE_REASON_PHRASE.n302_found.reason_phrase,
2327 307 => STATUS_CODE_REASON_PHRASE.n307_temporary_redirect.reason_phrase,
2328 308 => STATUS_CODE_REASON_PHRASE.n308_permanent_redirect.reason_phrase,
2329 _ => "Redirect",
2330 };
2331 (code as i16, phrase.to_string())
2332}
2333
2334impl Application for RedirectAdapter {
2335 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2336 let location = self
2337 .location_template
2338 .replace("$path", &request.request_uri);
2339 use crate::header::Header;
2340 let mut r = Response::new();
2341 r.status_code = self.status;
2342 r.reason_phrase = self.reason.as_ref().clone();
2343 r.headers.push(Header { name: "Location".into(), value: location.into() });
2344 Ok(r)
2345 }
2346}
2347
2348#[derive(Clone)]
2352#[doc(hidden)]
2353pub struct RespondAdapter {
2354 status: i16,
2355 reason: Arc<String>,
2356 body: Arc<Vec<u8>>,
2357 content_type: Arc<String>,
2358}
2359
2360impl RespondAdapter {
2361 #[doc(hidden)]
2362 pub fn new(status: u16, body: String, content_type: String) -> Self {
2363 use crate::response::STATUS_CODE_REASON_PHRASE;
2364 let reason = match status {
2365 200 => STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
2366 201 => STATUS_CODE_REASON_PHRASE.n201_created.reason_phrase.to_string(),
2367 204 => STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string(),
2368 400 => STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string(),
2369 401 => STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string(),
2370 403 => STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string(),
2371 404 => STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string(),
2372 500 => STATUS_CODE_REASON_PHRASE.n500_internal_server_error.reason_phrase.to_string(),
2373 _ => "OK".to_string(),
2374 };
2375 RespondAdapter {
2376 status: status as i16,
2377 reason: Arc::new(reason),
2378 body: Arc::new(body.into_bytes()),
2379 content_type: Arc::new(content_type),
2380 }
2381 }
2382}
2383
2384impl Application for RespondAdapter {
2385 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2386 use crate::range::Range;
2387 let mut r = Response::new();
2388 r.status_code = self.status;
2389 r.reason_phrase = self.reason.as_ref().clone();
2390 if !self.body.is_empty() {
2391 r.content_range_list = vec![Range::get_content_range(
2392 self.body.as_ref().clone(),
2393 self.content_type.as_ref().clone(),
2394 )];
2395 }
2396 Ok(r)
2397 }
2398}
2399
2400#[derive(Clone)]
2410#[doc(hidden)]
2411pub struct StaticAdapter {
2412 root: Arc<std::path::PathBuf>,
2413 index: Arc<Vec<String>>,
2414}
2415
2416impl StaticAdapter {
2417 #[doc(hidden)]
2418 pub fn new(root: String, index: Vec<String>) -> Self {
2419 let index = if index.is_empty() { vec!["index.html".to_string()] } else { index };
2420 StaticAdapter {
2421 root: Arc::new(std::path::PathBuf::from(root)),
2422 index: Arc::new(index),
2423 }
2424 }
2425
2426 fn resolve(&self, request_uri: &str) -> Option<std::path::PathBuf> {
2430 let raw_path = request_uri.split('?').next().unwrap_or(request_uri);
2431 let decoded = crate::url::URL::percent_decode(raw_path);
2432
2433 if decoded.split('/').any(|segment| segment == "..") {
2434 return None;
2435 }
2436
2437 let relative = decoded.trim_start_matches('/');
2438 Some(self.root.join(relative))
2439 }
2440}
2441
2442impl Application for StaticAdapter {
2443 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
2444 let mut response = Response::new();
2445
2446 let not_found = |mut response: Response| {
2449 response.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
2450 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
2451 let page = self.root.join("404.html");
2452 if let (true, Some(page)) = (page.is_file(), page.to_str()) {
2453 if let Ok(content_range) = crate::range::Range::get_content_range_of_a_file(page) {
2454 response.content_range_list = vec![content_range];
2455 }
2456 }
2457 response
2458 };
2459
2460 let candidate = match self.resolve(&request.request_uri) {
2461 Some(p) => p,
2462 None => {
2463 response.status_code = *STATUS_CODE_REASON_PHRASE.n403_forbidden.status_code;
2464 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string();
2465 return Ok(response);
2466 }
2467 };
2468
2469 let mut file_path = candidate;
2470 if file_path.is_dir() {
2471 let indexed = self
2472 .index
2473 .iter()
2474 .map(|name| file_path.join(name))
2475 .find(|p| p.is_file());
2476
2477 file_path = match indexed {
2478 Some(p) => p,
2479 None => return Ok(not_found(response)),
2480 };
2481 }
2482
2483 if !file_path.is_file() {
2484 return Ok(not_found(response));
2485 }
2486
2487 if let (Ok(root_canon), Ok(file_canon)) =
2490 (self.root.canonicalize(), file_path.canonicalize())
2491 {
2492 if !file_canon.starts_with(&root_canon) {
2493 return Ok(not_found(response));
2494 }
2495 }
2496
2497 let path_str = match file_path.to_str() {
2498 Some(s) => s,
2499 None => return Ok(not_found(response)),
2500 };
2501
2502 match crate::range::Range::get_content_range_of_a_file(path_str) {
2503 Ok(content_range) => {
2504 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
2505 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
2506 response.content_range_list = vec![content_range];
2507 Ok(response)
2508 }
2509 Err(_) => Ok(not_found(response)),
2510 }
2511 }
2512}
2513
2514#[doc(hidden)]
2518pub struct PerRouteRateLimit(pub(crate) Arc<crate::rate_limit::RateLimiter>);
2519
2520impl crate::middleware::Middleware for PerRouteRateLimit {
2521 fn handle(
2522 &self,
2523 request: &Request,
2524 conn: &ConnectionInfo,
2525 next: &dyn Application,
2526 ) -> Result<Response, String> {
2527 use crate::error::{AppError, IntoResponse};
2528 let ip = crate::ip_filter::client_ip(request, &conn.client.ip);
2529 if self.0.check(&ip) {
2530 next.execute(request, conn)
2531 } else {
2532 let mut response = AppError::TooManyRequests.into_response();
2533 let retry_after = self.0.retry_after(&ip);
2534 response.headers.push(Header {
2535 name: Header::_RETRY_AFTER.into(),
2536 value: retry_after.to_string().into(),
2537 });
2538 Ok(response)
2539 }
2540 }
2541}
2542
2543#[doc(hidden)]
2558pub struct PerRouteMaxBodySize(pub(crate) u64);
2559
2560impl crate::middleware::Middleware for PerRouteMaxBodySize {
2561 fn handle(
2562 &self,
2563 request: &Request,
2564 conn: &ConnectionInfo,
2565 next: &dyn Application,
2566 ) -> Result<Response, String> {
2567 use crate::error::{AppError, IntoResponse};
2568 if request.body.len() as u64 > self.0 {
2569 Ok(AppError::PayloadTooLarge(format!(
2570 "request body of {} bytes exceeds this route's {}-byte limit",
2571 request.body.len(),
2572 self.0,
2573 ))
2574 .into_response())
2575 } else {
2576 next.execute(request, conn)
2577 }
2578 }
2579}
2580
2581#[doc(hidden)]
2585pub struct BearerAuthMiddleware {
2586 #[doc(hidden)]
2587 pub token: Arc<String>,
2588}
2589
2590impl crate::middleware::Middleware for BearerAuthMiddleware {
2591 fn handle(
2592 &self,
2593 request: &Request,
2594 conn: &ConnectionInfo,
2595 next: &dyn Application,
2596 ) -> Result<Response, String> {
2597 use crate::error::{AppError, IntoResponse};
2598 let expected = format!("Bearer {}", self.token);
2599 let authorized = request
2600 .headers
2601 .iter()
2602 .any(|h| h.name.eq_ignore_ascii_case("authorization") && constant_time_eq(h.value.as_bytes(), expected.as_bytes()));
2603 if authorized {
2604 next.execute(request, conn)
2605 } else {
2606 Ok(AppError::Unauthorized.into_response())
2607 }
2608 }
2609}
2610
2611pub(crate) use crate::core::constant_time_eq;
2612
2613#[doc(hidden)]
2616pub struct AuthMisconfigured;
2617
2618#[doc(hidden)]
2620pub fn cache_max_bytes(max_size: &str) -> Option<u64> {
2621 if max_size.trim().is_empty() {
2622 Some(256 * 1024 * 1024)
2623 } else {
2624 crate::log::access_sink::parse_size(max_size.trim()).filter(|n| *n > 0)
2625 }
2626}
2627
2628#[doc(hidden)]
2631pub struct PluginMisconfigured;
2632
2633impl crate::middleware::Middleware for PluginMisconfigured {
2634 fn handle(&self, _request: &Request, _conn: &ConnectionInfo, _next: &dyn Application) -> Result<Response, String> {
2635 Ok(crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "this route's plugin is misconfigured"))
2636 }
2637}
2638
2639impl crate::middleware::Middleware for AuthMisconfigured {
2640 fn handle(
2641 &self,
2642 _request: &Request,
2643 _conn: &ConnectionInfo,
2644 _next: &dyn Application,
2645 ) -> Result<Response, String> {
2646 Ok(crate::error::problem(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "authentication for this route is misconfigured"))
2647 }
2648}
2649
2650#[doc(hidden)]
2654pub fn arc_app<A: Application + Send + Sync + 'static>(
2655 a: A,
2656) -> Arc<dyn Application + Send + Sync> {
2657 Arc::new(a)
2658}
2659
2660pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
2665 builder::build_from_file()
2666}