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