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