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 llm;
37
38#[cfg(test)]
39mod tests;
40
41use std::sync::atomic::{AtomicBool, Ordering};
42use std::sync::{Arc, RwLock};
43
44use crate::app::App;
45use crate::application::Application;
46use crate::core::New;
47use crate::request::Request;
48use crate::header::Header;
49use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
50use crate::server::ConnectionInfo;
51use crate::server_config::ServerConfig;
52
53#[derive(Debug, Clone)]
56pub struct ProxyConfig {
57 pub upstreams: Vec<UpstreamConfig>,
58 pub routes: Vec<RouteConfig>,
59 pub tcp_proxies: Vec<TcpProxyConfig>,
60 pub udp_proxies: Vec<UdpProxyConfig>,
61 pub ws_proxies: Vec<WsProxyConfig>,
62 pub global_middleware: MiddlewareConfig,
63}
64
65#[derive(Debug, Clone)]
66pub struct UpstreamConfig {
67 pub name: String,
68 pub backends: Vec<String>,
69 pub strategy: String, pub health_check: Option<HealthCheckConfig>,
71 pub tls: bool,
75 pub tls_ca_file: Option<String>,
79 pub tls_client_cert_file: Option<String>,
83 pub tls_client_key_file: Option<String>,
84 pub weights: Vec<u32>,
86 pub sticky: Option<StickyConfig>,
88 pub circuit_breaker: Option<BreakerConfig>,
91 pub retry: Option<RetryConfig>,
93}
94
95#[derive(Debug, Clone)]
96pub struct HealthCheckConfig {
97 pub path: String,
98 pub interval_secs: u64,
99 pub timeout_ms: u64,
100 pub healthy_threshold: u32,
101 pub unhealthy_threshold: u32,
102}
103
104#[derive(Debug, Clone)]
105pub struct RouteConfig {
106 pub name: String,
107 pub match_: MatchConfig,
108 pub action: ActionConfig,
109 pub middleware: MiddlewareConfig,
110}
111
112#[derive(Debug, Clone, Default)]
113pub struct MatchConfig {
114 pub host: Option<String>,
115 pub path: Option<String>,
116 pub method: Option<String>,
117 pub content_type: Option<String>,
118}
119
120#[derive(Debug, Clone)]
121pub enum ActionConfig {
122 Proxy {
123 upstream: String,
124 connect_timeout_ms: u64,
125 read_timeout_ms: u64,
126 strip_path_prefix: Option<String>,
127 add_path_prefix: Option<String>,
128 },
129 Grpc {
130 upstream: String,
131 connect_timeout_ms: u64,
132 read_timeout_ms: u64,
133 },
134 Static {
135 root: String,
136 index: Vec<String>,
137 },
138 Redirect {
139 location: String,
140 status: u16,
141 },
142 Respond {
143 status: u16,
144 body: String,
145 content_type: String,
146 },
147 Mcp(Box<mcp_gateway::McpGatewayConfig>),
149 Llm(Box<llm::LlmConfig>),
151 Unknown(String),
152}
153
154#[derive(Debug, Clone, Default)]
155pub struct MiddlewareConfig {
156 pub rate_limit: Option<RateLimitConfig>,
157 pub cache: Option<CacheConfig>,
158 pub auth: Option<AuthConfig>,
159 pub rewrite_request: Vec<RewriteRuleConfig>,
160 pub rewrite_response: Vec<RewriteRuleConfig>,
161 pub ip_allow: Vec<String>,
162 pub ip_deny: Vec<String>,
163 pub timeout_ms: Option<u64>,
168 pub max_body_size: Option<u64>,
184}
185
186#[derive(Debug, Clone)]
187pub struct RateLimitConfig {
188 pub max_requests: u32,
189 pub window_secs: u64,
190}
191
192#[derive(Debug, Clone)]
193pub struct CacheConfig {
194 pub ttl_secs: u64,
195 pub vary_by: Vec<String>,
196}
197
198#[derive(Debug, Clone)]
199pub enum AuthConfig {
200 Basic { htpasswd_file: String },
205 Jwt { secret_env: String },
208 Bearer { token_env: String },
209}
210
211#[derive(Debug, Clone, Default)]
212pub struct RewriteRuleConfig {
213 pub type_: String,
214 pub name: Option<String>,
215 pub value: Option<String>,
216 pub prefix: Option<String>,
217 pub from: Option<String>,
218 pub to: Option<String>,
219 pub code: Option<u16>,
220 pub reason: Option<String>,
221}
222
223#[derive(Debug, Clone)]
224pub struct TcpProxyConfig {
225 pub name: String,
226 pub listen: String,
227 pub backends: Vec<String>,
228 pub connect_timeout_ms: u64,
229}
230
231#[derive(Debug, Clone)]
232pub struct UdpProxyConfig {
233 pub name: String,
234 pub listen: String,
235 pub backends: Vec<String>,
236 pub reply_timeout_ms: u64,
237 pub buffer_size: usize,
238}
239
240#[derive(Debug, Clone)]
241pub struct WsProxyConfig {
242 pub name: String,
243 pub listen: String,
244 pub backends: Vec<String>,
245 pub connect_timeout_ms: u64,
246 pub read_timeout_ms: u64,
247 pub health_check: Option<HealthCheckConfig>,
254}
255
256fn merge_action_keys(map: &mut parser::SectionMap, action_section: &str, action_type: &str) {
265 if action_type.is_empty() {
266 return;
267 }
268 let short_form: Vec<(String, String)> = match map.get(action_section) {
269 Some(entries) => entries.iter().filter(|(k, _)| k != "type").cloned().collect(),
270 None => return,
271 };
272 let nested = map.entry(format!("{action_section}.{action_type}")).or_default();
273 for (key, value) in short_form {
274 if !nested.iter().any(|(k, _)| *k == key) {
275 nested.push((key, value));
276 }
277 }
278}
279
280impl ProxyConfig {
281 pub fn is_proxy_mode() -> bool {
285 let path = config_file_path();
286 match std::fs::read_to_string(&path) {
287 Ok(contents) => {
288 contents.contains("[[route]]") || contents.contains("[[upstream]]")
289 }
290 Err(_) => false,
291 }
292 }
293
294 pub fn load() -> Self {
296 let path = config_file_path();
297 let contents = std::fs::read_to_string(&path).unwrap_or_default();
298 Self::from_str(&contents)
299 }
300
301 pub fn from_str(toml: &str) -> Self {
303 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
304
305 let mut map = parser::parse(toml);
306
307 let mut upstreams = Vec::new();
309 let mut i = 0;
310 loop {
311 let sec = format!("upstream[{}]", i);
312 if !section_exists(&map, &sec) {
313 break;
314 }
315 let name = get_str(&map, &sec, "name");
316 let backends = get_array(&map, &sec, "backends");
317 let strategy = {
318 let s = get_str(&map, &sec, "strategy");
319 if s.is_empty() { "round_robin".to_string() } else { s }
320 };
321 let hc_sec = format!("{}.health_check", sec);
322 let health_check = if section_exists(&map, &hc_sec) {
323 Some(HealthCheckConfig {
324 path: {
325 let p = get_str(&map, &hc_sec, "path");
326 if p.is_empty() { "/health".to_string() } else { p }
327 },
328 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
329 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
330 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
331 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
332 })
333 } else {
334 None
335 };
336 let tls = backends.iter().any(|b| ["https://", "h2s://", "grpcs://"].iter().any(|s| b.starts_with(s)));
337 let tls_ca_file = Some(get_str(&map, &sec, "tls_ca_file")).filter(|v| !v.is_empty());
338 let tls_client_cert_file = Some(get_str(&map, &sec, "tls_client_cert_file")).filter(|v| !v.is_empty());
339 let tls_client_key_file = Some(get_str(&map, &sec, "tls_client_key_file")).filter(|v| !v.is_empty());
340 let weights: Vec<u32> = get_array(&map, &sec, "weights").iter().map(|w| w.trim().parse().unwrap_or(0)).collect();
341 let sticky_sec = format!("{}.sticky", sec);
342 let sticky = section_exists(&map, &sticky_sec).then(|| StickyConfig {
343 cookie: Some(get_str(&map, &sticky_sec, "cookie")).filter(|c| !c.is_empty()).unwrap_or_else(|| "rws_backend".to_string()),
344 max_age_secs: Some(get_u64(&map, &sticky_sec, "max_age_secs", 0)).filter(|s| *s > 0),
345 });
346 let cb_sec = format!("{}.circuit_breaker", sec);
347 let circuit_breaker = section_exists(&map, &cb_sec).then(|| BreakerConfig {
348 failure_threshold: get_u32(&map, &cb_sec, "failure_threshold", 5),
349 recovery_secs: get_u64(&map, &cb_sec, "recovery_secs", 30),
350 half_open_probes: get_u32(&map, &cb_sec, "half_open_probes", 1),
351 });
352 let retry_sec = format!("{}.retry", sec);
353 let retry = section_exists(&map, &retry_sec).then(|| {
354 let status: Vec<i16> = get_array(&map, &retry_sec, "status").iter().filter_map(|s| s.trim().parse().ok()).collect();
355 RetryConfig { attempts: get_u32(&map, &retry_sec, "attempts", 1), status: if status.is_empty() { vec![502, 503, 504] } else { status } }
356 });
357 upstreams.push(UpstreamConfig {
358 name, backends, strategy, health_check, tls, tls_ca_file, tls_client_cert_file, tls_client_key_file,
359 weights, sticky, circuit_breaker, retry,
360 });
361 i += 1;
362 }
363
364 let mut routes = Vec::new();
366 let mut i = 0;
367 loop {
368 let sec = format!("route[{}]", i);
369 if !section_exists(&map, &sec) {
370 break;
371 }
372 let name = get_str(&map, &sec, "name");
373
374 let m_sec = format!("{}.match", sec);
376 let match_ = MatchConfig {
377 host: {
378 let h = get_str(&map, &m_sec, "host");
379 if h.is_empty() { None } else { Some(h) }
380 },
381 path: {
382 let p = get_str(&map, &m_sec, "path");
383 if p.is_empty() { None } else { Some(p) }
384 },
385 method: {
386 let m = get_str(&map, &m_sec, "method");
387 if m.is_empty() { None } else { Some(m.to_uppercase()) }
388 },
389 content_type: {
390 let c = get_str(&map, &m_sec, "content_type");
391 if c.is_empty() { None } else { Some(c) }
392 },
393 };
394
395 let a_sec = format!("{}.action", sec);
397 let action_type = get_str(&map, &a_sec, "type");
398 merge_action_keys(&mut map, &a_sec, &action_type);
399 let action = match action_type.as_str() {
400 "proxy" => {
401 let p_sec = format!("{}.action.proxy", sec);
402 ActionConfig::Proxy {
403 upstream: get_str(&map, &p_sec, "upstream"),
404 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
405 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
406 strip_path_prefix: {
407 let v = get_str(&map, &p_sec, "strip_path_prefix");
408 if v.is_empty() { None } else { Some(v) }
409 },
410 add_path_prefix: {
411 let v = get_str(&map, &p_sec, "add_path_prefix");
412 if v.is_empty() { None } else { Some(v) }
413 },
414 }
415 }
416 "grpc" => {
417 let p_sec = format!("{}.action.grpc", sec);
418 ActionConfig::Grpc {
419 upstream: get_str(&map, &p_sec, "upstream"),
420 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
421 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
422 }
423 }
424 "static" => {
425 let s_sec = format!("{}.action.static", sec);
426 ActionConfig::Static {
427 root: get_str(&map, &s_sec, "root"),
428 index: get_array(&map, &s_sec, "index"),
429 }
430 }
431 "redirect" => {
432 let r_sec = format!("{}.action.redirect", sec);
433 ActionConfig::Redirect {
434 location: get_str(&map, &r_sec, "location"),
435 status: get_u64(&map, &r_sec, "status", 301) as u16,
436 }
437 }
438 "respond" => {
439 let r_sec = format!("{}.action.respond", sec);
440 ActionConfig::Respond {
441 status: get_u64(&map, &r_sec, "status", 200) as u16,
442 body: get_str(&map, &r_sec, "body"),
443 content_type: {
444 let ct = get_str(&map, &r_sec, "content_type");
445 if ct.is_empty() { "text/plain".to_string() } else { ct }
446 },
447 }
448 }
449 "mcp" => ActionConfig::Mcp(Box::new(mcp_gateway::parse(&map, &a_sec))),
450 "llm" => ActionConfig::Llm(Box::new(llm::parse(&map, &a_sec))),
451 other => ActionConfig::Unknown(other.to_string()),
452 };
453
454 let mw_sec = format!("{}.middleware", sec);
456 let middleware = parse_middleware_config(&map, &mw_sec, i);
457
458 routes.push(RouteConfig { name, match_, action, middleware });
459 i += 1;
460 }
461
462 let mut tcp_proxies = Vec::new();
464 let mut i = 0;
465 loop {
466 let sec = format!("tcp_proxy[{}]", i);
467 if !section_exists(&map, &sec) {
468 break;
469 }
470 tcp_proxies.push(TcpProxyConfig {
471 name: get_str(&map, &sec, "name"),
472 listen: get_str(&map, &sec, "listen"),
473 backends: get_array(&map, &sec, "backends"),
474 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
475 });
476 i += 1;
477 }
478
479 let mut udp_proxies = Vec::new();
481 let mut i = 0;
482 loop {
483 let sec = format!("udp_proxy[{}]", i);
484 if !section_exists(&map, &sec) {
485 break;
486 }
487 udp_proxies.push(UdpProxyConfig {
488 name: get_str(&map, &sec, "name"),
489 listen: get_str(&map, &sec, "listen"),
490 backends: get_array(&map, &sec, "backends"),
491 reply_timeout_ms: get_u64(&map, &sec, "reply_timeout_ms", 5000),
492 buffer_size: get_u64(&map, &sec, "buffer_size", 65536) as usize,
493 });
494 i += 1;
495 }
496
497 let mut ws_proxies = Vec::new();
499 let mut i = 0;
500 loop {
501 let sec = format!("ws_proxy[{}]", i);
502 if !section_exists(&map, &sec) {
503 break;
504 }
505 let hc_sec = format!("{}.health_check", sec);
506 let health_check = if section_exists(&map, &hc_sec) {
507 Some(HealthCheckConfig {
508 path: {
509 let p = get_str(&map, &hc_sec, "path");
510 if p.is_empty() { "/health".to_string() } else { p }
511 },
512 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
513 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
514 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
515 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
516 })
517 } else {
518 None
519 };
520 ws_proxies.push(WsProxyConfig {
521 name: get_str(&map, &sec, "name"),
522 listen: get_str(&map, &sec, "listen"),
523 backends: get_array(&map, &sec, "backends"),
524 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
525 read_timeout_ms: get_u64(&map, &sec, "read_timeout_ms", 30000),
526 health_check,
527 });
528 i += 1;
529 }
530
531 let global_middleware = parse_middleware_config(&map, "middleware", usize::MAX);
533
534 ProxyConfig {
535 upstreams,
536 routes,
537 tcp_proxies,
538 udp_proxies,
539 ws_proxies,
540 global_middleware,
541 }
542 }
543}
544
545fn parse_middleware_config(
548 map: &parser::SectionMap,
549 mw_sec: &str,
550 route_idx: usize,
551) -> MiddlewareConfig {
552 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
553
554 let rl_sec = format!("{}.rate_limit", mw_sec);
555 let rate_limit = if section_exists(map, &rl_sec) {
556 Some(RateLimitConfig {
557 max_requests: get_u32(map, &rl_sec, "max_requests", 1000),
558 window_secs: get_u64(map, &rl_sec, "window_secs", 60),
559 })
560 } else {
561 None
562 };
563
564 let c_sec = format!("{}.cache", mw_sec);
565 let cache = if section_exists(map, &c_sec) {
566 Some(CacheConfig {
567 ttl_secs: get_u64(map, &c_sec, "ttl_secs", 60),
568 vary_by: get_array(map, &c_sec, "vary_by"),
569 })
570 } else {
571 None
572 };
573
574 let a_sec = format!("{}.auth", mw_sec);
575 let auth = if section_exists(map, &a_sec) {
576 let auth_type = get_str(map, &a_sec, "type");
577 match auth_type.as_str() {
578 "bearer" => Some(AuthConfig::Bearer {
579 token_env: get_str(map, &a_sec, "token_env"),
580 }),
581 "jwt" => Some(AuthConfig::Jwt {
582 secret_env: get_str(map, &a_sec, "secret_env"),
583 }),
584 "basic" => Some(AuthConfig::Basic {
585 htpasswd_file: get_str(map, &a_sec, "htpasswd_file"),
586 }),
587 _ => None,
588 }
589 } else {
590 None
591 };
592
593 let rewrite_request = collect_rewrite_rules(map, mw_sec, "request");
599 let rewrite_response = collect_rewrite_rules(map, mw_sec, "response");
600
601 let ip_sec = format!("{}.ip_filter", mw_sec);
602 let ip_allow = if section_exists(map, &ip_sec) {
603 get_array(map, &ip_sec, "allow")
604 } else {
605 vec![]
606 };
607 let ip_deny = if section_exists(map, &ip_sec) {
608 get_array(map, &ip_sec, "deny")
609 } else {
610 vec![]
611 };
612
613 let _ = route_idx; let timeout_ms = match get_u64(map, mw_sec, "timeout_ms", 0) {
619 0 => None,
620 ms => Some(ms),
621 };
622
623 let max_body_size = match get_u64(map, mw_sec, "max_body_size", 0) {
627 0 => None,
628 n => Some(n),
629 };
630
631 MiddlewareConfig {
632 rate_limit, cache, auth, rewrite_request, rewrite_response, ip_allow, ip_deny,
633 timeout_ms, max_body_size,
634 }
635}
636
637fn collect_rewrite_rules(
639 map: &parser::SectionMap,
640 mw_sec: &str,
641 direction: &str,
642) -> Vec<RewriteRuleConfig> {
643 use parser::{get_str, get_u64};
644
645 let mut rules = Vec::new();
646 let mut j = 0;
647 loop {
648 let rsec = format!("{}.rewrite.{}[{}]", mw_sec, direction, j);
649 if !parser::section_exists(map, &rsec) {
650 break;
651 }
652 let code_val = get_u64(map, &rsec, "code", 0);
653 rules.push(RewriteRuleConfig {
654 type_: get_str(map, &rsec, "type"),
655 name: {
656 let v = get_str(map, &rsec, "name");
657 if v.is_empty() { None } else { Some(v) }
658 },
659 value: {
660 let v = get_str(map, &rsec, "value");
661 if v.is_empty() { None } else { Some(v) }
662 },
663 prefix: {
664 let v = get_str(map, &rsec, "prefix");
665 if v.is_empty() { None } else { Some(v) }
666 },
667 from: {
668 let v = get_str(map, &rsec, "from");
669 if v.is_empty() { None } else { Some(v) }
670 },
671 to: {
672 let v = get_str(map, &rsec, "to");
673 if v.is_empty() { None } else { Some(v) }
674 },
675 code: if code_val == 0 { None } else { Some(code_val as u16) },
676 reason: {
677 let v = get_str(map, &rsec, "reason");
678 if v.is_empty() { None } else { Some(v) }
679 },
680 });
681 j += 1;
682 }
683 rules
684}
685
686pub(crate) fn config_file_path() -> String {
687 std::env::var("RWS_CONFIG_FILE").unwrap_or_else(|_| "rws.config.toml".to_string())
688}
689
690pub(crate) struct CompiledRoute {
694 pub(crate) matcher: RouteMatcher,
695 pub(crate) handler: Arc<dyn Application + Send + Sync>,
697 #[cfg_attr(not(feature = "http2"), allow(dead_code))]
701 pub(crate) async_proxy: Option<Arc<DynamicProxy>>,
702}
703
704#[derive(Clone, Default)]
706pub(crate) struct RouteMatcher {
707 pub(crate) host: Option<String>,
709 pub(crate) path_prefix: Option<String>,
711 pub(crate) path_exact: Option<String>,
713 pub(crate) method: Option<String>,
715 pub(crate) content_type_prefix: Option<String>,
717}
718
719impl RouteMatcher {
720 pub(crate) fn from_match_config(cfg: &MatchConfig) -> Self {
721 let (path_prefix, path_exact) = match &cfg.path {
722 Some(p) if p.ends_with('*') => {
723 let stripped = p.trim_end_matches('*').to_string();
725 (Some(stripped), None)
726 }
727 Some(p) => (None, Some(p.clone())),
728 None => (None, None),
729 };
730 let content_type_prefix = cfg.content_type.as_ref().map(|ct| {
731 if ct.ends_with('*') {
732 ct.trim_end_matches('*').to_string()
733 } else {
734 ct.clone()
735 }
736 });
737 RouteMatcher {
738 host: cfg.host.clone(),
739 path_prefix,
740 path_exact,
741 method: cfg.method.clone(),
742 content_type_prefix,
743 }
744 }
745
746 pub(crate) fn matches(&self, request: &Request, conn: &ConnectionInfo) -> bool {
748 if let Some(ref expected_host) = self.host {
750 let actual_host = conn
751 .sni_hostname
752 .as_deref()
753 .or_else(|| {
754 request
755 .headers
756 .iter()
757 .find(|h| h.name.eq_ignore_ascii_case("host"))
758 .map(|h| h.value.as_ref())
759 })
760 .unwrap_or("");
761 if !host_matches(expected_host, actual_host) {
762 return false;
763 }
764 }
765
766 if let Some(ref m) = self.method {
768 if request.method.to_uppercase() != m.as_str() {
769 return false;
770 }
771 }
772
773 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
775 if let Some(ref prefix) = self.path_prefix {
776 if !path.starts_with(prefix.as_str()) {
777 return false;
778 }
779 } else if let Some(ref exact) = self.path_exact {
780 if path != exact.as_str() {
781 return false;
782 }
783 }
784
785 if let Some(ref ct_prefix) = self.content_type_prefix {
787 let actual_ct = request
788 .headers
789 .iter()
790 .find(|h| h.name.eq_ignore_ascii_case("content-type"))
791 .map(|h| h.value.as_ref())
792 .unwrap_or("");
793 if !actual_ct.starts_with(ct_prefix.as_str()) {
794 return false;
795 }
796 }
797
798 true
799 }
800}
801
802#[derive(Clone)]
809pub struct ConfigDrivenApp {
810 current: Arc<RwLock<Arc<Generation>>>,
811 reload_lock: Arc<std::sync::Mutex<()>>,
813}
814
815struct Generation {
817 dispatch: RouteDispatch,
818 entry: Option<Arc<dyn Application + Send + Sync>>,
821 config: ProxyConfig,
822 source: Option<String>,
824 health_stop: Arc<AtomicBool>,
826}
827
828impl Generation {
829 fn new(routes: Arc<Vec<CompiledRoute>>, fallback: App, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
830 let dispatch = RouteDispatch { routes, fallback };
831 let entry = if config.global_middleware.is_empty() {
832 None
833 } else {
834 Some(builder::apply_middleware(arc_app(dispatch.clone()), &config.global_middleware))
835 };
836 Generation { dispatch, entry, config, source, health_stop }
837 }
838}
839
840#[derive(Clone)]
842struct RouteDispatch {
843 routes: Arc<Vec<CompiledRoute>>,
844 fallback: App,
849}
850
851impl Application for RouteDispatch {
852 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
853 for route in self.routes.iter() {
854 if route.matcher.matches(request, conn) {
855 return route.handler.execute(request, conn);
856 }
857 }
858 self.fallback.execute(request, conn)
859 }
860
861 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
862 match self.routes.iter().find(|r| r.matcher.matches(request, conn)) {
863 Some(route) => route.handler.streams_body(request, conn),
864 None => self.fallback.streams_body(request, conn),
865 }
866 }
867}
868
869impl MiddlewareConfig {
870 pub fn is_empty(&self) -> bool {
872 self.rate_limit.is_none() && self.cache.is_none() && self.auth.is_none()
873 && self.rewrite_request.is_empty() && self.rewrite_response.is_empty()
874 && self.ip_allow.is_empty() && self.ip_deny.is_empty()
875 && self.timeout_ms.is_none() && self.max_body_size.is_none()
876 }
877}
878
879#[derive(Debug, Clone, PartialEq)]
881pub enum ReloadOutcome {
882 Unchanged,
884 Reloaded { routes: usize, warnings: Vec<String> },
887}
888
889impl ConfigDrivenApp {
890 pub(crate) fn new(routes: Vec<CompiledRoute>, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
891 use crate::core::New;
892 let generation = Generation::new(Arc::new(routes), App::new(), config, health_stop, source);
893 ConfigDrivenApp {
894 current: Arc::new(RwLock::new(Arc::new(generation))),
895 reload_lock: Arc::new(std::sync::Mutex::new(())),
896 }
897 }
898
899 fn current(&self) -> Arc<Generation> {
900 Arc::clone(&self.current.read().unwrap_or_else(|e| e.into_inner()))
901 }
902
903 pub fn with_config(self, config: ServerConfig) -> Self {
920 {
921 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
922 let old = Arc::clone(&slot);
923 *slot = Arc::new(Generation::new(
924 Arc::clone(&old.dispatch.routes),
925 App::with_config(config),
926 old.config.clone(),
927 Arc::clone(&old.health_stop),
928 old.source.clone(),
929 ));
930 }
931 self
932 }
933
934 pub fn reload_from_str(&self, toml: &str) -> Result<ReloadOutcome, crate::config_check::ConfigReport> {
944 let _serialized = self.reload_lock.lock().unwrap_or_else(|e| e.into_inner());
945 let old = self.current();
946 if old.source.as_deref() == Some(toml) {
947 return Ok(ReloadOutcome::Unchanged);
948 }
949 let config = ProxyConfig::from_str(toml);
950 let mut report = crate::config_check::ConfigReport::default();
951 validate::check_unknown_keys(toml, &mut report);
952 config.validate(&mut report);
953 if !report.is_ok() {
954 return Err(report);
955 }
956
957 let mut warnings = report.warnings;
958 let listeners = |c: &ProxyConfig| format!("{:?}{:?}{:?}", c.tcp_proxies, c.udp_proxies, c.ws_proxies);
959 if listeners(&config) != listeners(&old.config) {
960 warnings.push("[[tcp_proxy]] / [[udp_proxy]] / [[ws_proxy]] changes need a restart; the old listeners keep running".to_string());
961 }
962
963 let health_stop = Arc::new(AtomicBool::new(false));
964 let routes = builder::compile_routes(&config, &health_stop);
965 let route_count = routes.len();
966 let generation = Generation::new(Arc::new(routes), old.dispatch.fallback.clone(), config, health_stop, Some(toml.to_string()));
967 let replaced = {
968 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
969 std::mem::replace(&mut *slot, Arc::new(generation))
970 };
971 replaced.health_stop.store(true, Ordering::Relaxed);
972 Ok(ReloadOutcome::Reloaded { routes: route_count, warnings })
973 }
974
975 pub fn reload_from_file(&self) {
980 let path = config_file_path();
981 let toml = match std::fs::read_to_string(&path) {
982 Ok(toml) => toml,
983 Err(e) => {
984 eprintln!("[proxy_config] reload: cannot read {path}: {e} — keeping the running routes");
985 return;
986 }
987 };
988 match self.reload_from_str(&toml) {
989 Ok(ReloadOutcome::Unchanged) => {}
990 Ok(ReloadOutcome::Reloaded { routes, warnings }) => {
991 for w in &warnings {
992 eprintln!("[proxy_config] Warning: {w}");
993 }
994 println!("[proxy_config] reloaded {path}: {routes} route{}", if routes == 1 { "" } else { "s" });
995 }
996 Err(report) => {
997 for e in &report.errors {
998 eprintln!("[proxy_config] reload ERROR: {e}");
999 }
1000 eprintln!("[proxy_config] {path} has errors — keeping the running routes");
1001 }
1002 }
1003 }
1004}
1005
1006fn normalized(request: &Request) -> std::borrow::Cow<'_, Request> {
1009 match normalize_request_target(&request.request_uri) {
1010 std::borrow::Cow::Borrowed(_) => std::borrow::Cow::Borrowed(request),
1011 std::borrow::Cow::Owned(target) => {
1012 let mut owned = request.clone();
1013 owned.request_uri = target;
1014 std::borrow::Cow::Owned(owned)
1015 }
1016 }
1017}
1018
1019pub(crate) fn normalize_request_target(target: &str) -> std::borrow::Cow<'_, str> {
1027 let (path, query) = match target.find('?') {
1028 Some(i) => (&target[..i], &target[i..]),
1029 None => (target, ""),
1030 };
1031 if !path.starts_with('/') || !(path.contains('%') || path.contains("//") || path.contains("/.")) {
1032 return std::borrow::Cow::Borrowed(target);
1033 }
1034 let bytes = path.as_bytes();
1035 let mut decoded = String::with_capacity(path.len());
1036 let mut i = 0;
1037 while i < bytes.len() {
1038 let hex = |b: u8| (b as char).to_digit(16);
1039 if bytes[i] == b'%' && i + 2 < bytes.len() {
1040 if let (Some(h), Some(l)) = (hex(bytes[i + 1]), hex(bytes[i + 2])) {
1041 let value = (h * 16 + l) as u8;
1042 if value.is_ascii_alphanumeric() || matches!(value, b'-' | b'.' | b'_' | b'~') {
1043 decoded.push(value as char);
1044 } else {
1045 decoded.push('%');
1046 decoded.push(bytes[i + 1].to_ascii_uppercase() as char);
1047 decoded.push(bytes[i + 2].to_ascii_uppercase() as char);
1048 }
1049 i += 3;
1050 continue;
1051 }
1052 }
1053 if bytes[i] == b'%' {
1057 decoded.push_str("%25");
1058 i += 1;
1059 continue;
1060 }
1061 let ch = path[i..].chars().next().unwrap_or('\u{FFFD}');
1063 decoded.push(ch);
1064 i += ch.len_utf8().max(1);
1065 }
1066 let mut segments: Vec<&str> = Vec::new();
1067 let parts: Vec<&str> = decoded.split('/').collect();
1068 let last = parts.len().saturating_sub(1);
1069 let mut trailing_slash = false;
1070 for (n, segment) in parts.iter().enumerate() {
1071 match *segment {
1072 "" => trailing_slash = n == last,
1073 "." => trailing_slash = n == last,
1074 ".." => {
1075 segments.pop();
1076 trailing_slash = n == last;
1077 }
1078 s => {
1079 segments.push(s);
1080 trailing_slash = false;
1081 }
1082 }
1083 }
1084 let mut out = String::with_capacity(decoded.len() + query.len());
1085 out.push('/');
1086 out.push_str(&segments.join("/"));
1087 if trailing_slash && !segments.is_empty() {
1088 out.push('/');
1089 }
1090 out.push_str(query);
1091 if out == target {
1092 std::borrow::Cow::Borrowed(target)
1093 } else {
1094 std::borrow::Cow::Owned(out)
1095 }
1096}
1097
1098fn host_matches(expected: &str, actual: &str) -> bool {
1101 let actual = if expected.contains(':') {
1102 actual
1103 } else if let Some(rest) = actual.strip_prefix('[') {
1104 rest.split(']').next().unwrap_or(rest)
1106 } else {
1107 actual.rsplit_once(':').map_or(actual, |(host, port)| if port.bytes().all(|b| b.is_ascii_digit()) { host } else { actual })
1108 };
1109 let expected = expected.trim_start_matches('[').trim_end_matches(']');
1110 actual.eq_ignore_ascii_case(expected)
1111}
1112
1113impl Application for ConfigDrivenApp {
1114 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1115 let request = normalized(request);
1116 let generation = self.current();
1117 match &generation.entry {
1118 Some(entry) => entry.execute(&request, conn),
1119 None => generation.dispatch.execute(&request, conn),
1120 }
1121 }
1122
1123 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1126 let request = normalized(request);
1127 let generation = self.current();
1128 match &generation.entry {
1129 Some(entry) => entry.streams_body(&request, conn),
1130 None => generation.dispatch.streams_body(&request, conn),
1131 }
1132 }
1133
1134 fn runs_inline(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1138 let request = normalized(request);
1139 let generation = self.current();
1140 generation.entry.is_none()
1141 && !generation.dispatch.routes.iter().any(|r| r.matcher.matches(&request, conn))
1142 && App::is_inline_request(&request)
1143 }
1144
1145 #[cfg(feature = "http2")]
1149 fn execute_async<'a>(&'a self, request: &'a Request, conn: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
1150 let generation = self.current();
1151 if generation.entry.is_some() {
1152 return None;
1153 }
1154 let request = normalized(request);
1155 let route = generation.dispatch.routes.iter().find(|r| r.matcher.matches(&request, conn))?;
1156 let proxy = Arc::clone(route.async_proxy.as_ref()?);
1157 Some(Box::pin(async move { proxy.execute_async(&request, conn).await }))
1158 }
1159}
1160
1161#[cfg(feature = "http2")]
1167pub(crate) struct H2Route {
1168 live: Arc<RwLock<Vec<String>>>,
1169 tls: bool,
1170 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1171 connect_timeout: std::time::Duration,
1172 read_timeout: std::time::Duration,
1173 counter: std::sync::atomic::AtomicUsize,
1174}
1175
1176#[cfg(feature = "http2")]
1177impl H2Route {
1178 pub(crate) fn new(
1179 live: Arc<RwLock<Vec<String>>>,
1180 tls: bool,
1181 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1182 connect_timeout_ms: u64,
1183 read_timeout_ms: u64,
1184 ) -> H2Route {
1185 H2Route {
1186 live,
1187 tls,
1188 trust,
1189 connect_timeout: std::time::Duration::from_millis(connect_timeout_ms),
1190 read_timeout: std::time::Duration::from_millis(read_timeout_ms),
1191 counter: std::sync::atomic::AtomicUsize::new(0),
1192 }
1193 }
1194}
1195
1196#[cfg(feature = "http2")]
1197impl Application for H2Route {
1198 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1199 use crate::middleware::Middleware;
1200 crate::proxy::GrpcWebLayer::new().handle(request, conn, &H2Forward(self))
1201 }
1202
1203 fn streams_body(&self, _request: &Request, _conn: &ConnectionInfo) -> bool {
1204 true
1205 }
1206}
1207
1208#[cfg(feature = "http2")]
1210struct H2Forward<'a>(&'a H2Route);
1211
1212#[cfg(feature = "http2")]
1213impl Application for H2Forward<'_> {
1214 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1215 let route = self.0;
1216 let backends = route.live.read().unwrap_or_else(|e| e.into_inner()).clone();
1217 if backends.is_empty() {
1218 return Ok(crate::proxy::bad_gateway());
1219 }
1220 let attempts = if matches!(request.body, crate::request::Body::Stream(_)) { 1 } else { backends.len() };
1222 let start = route.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1223 for attempt in 0..attempts {
1224 let url = &backends[(start + attempt) % backends.len()];
1225 let result = crate::proxy::forward_h2(
1226 request, crate::proxy::Peer::of(conn), url, route.tls, route.trust.as_deref(), route.connect_timeout, route.read_timeout,
1227 );
1228 match result {
1229 Ok(response) => return Ok(response),
1230 Err(e) => eprintln!("grpc route: {e}"),
1231 }
1232 }
1233 Ok(crate::proxy::bad_gateway())
1234 }
1235}
1236
1237#[derive(Clone, Copy)]
1242pub(crate) struct NullApp;
1243
1244impl Application for NullApp {
1245 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1246 let mut r = Response::new();
1247 r.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1248 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1249 Ok(r)
1250 }
1251}
1252
1253use std::collections::HashMap;
1256use std::collections::hash_map::DefaultHasher;
1257use std::hash::{Hash, Hasher};
1258use std::sync::atomic::AtomicUsize;
1259use std::time::{Duration, SystemTime, UNIX_EPOCH};
1260
1261#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1265pub(crate) enum LoadBalanceStrategy {
1266 RoundRobin,
1267 Random,
1268 IpHash,
1269 LeastConnections,
1270}
1271
1272impl LoadBalanceStrategy {
1273 fn parse(s: &str) -> Self {
1274 match s {
1275 "random" => LoadBalanceStrategy::Random,
1276 "ip_hash" => LoadBalanceStrategy::IpHash,
1277 "least_connections" => LoadBalanceStrategy::LeastConnections,
1278 _ => LoadBalanceStrategy::RoundRobin,
1279 }
1280 }
1281}
1282
1283#[derive(Clone)]
1291pub(crate) struct DynamicProxy {
1292 live: Arc<RwLock<Vec<String>>>,
1293 counter: Arc<AtomicUsize>,
1294 connect_timeout: Duration,
1295 read_timeout: Duration,
1296 strip_prefix: Option<Arc<String>>,
1297 add_prefix: Option<Arc<String>>,
1298 tls: bool,
1299 strategy: LoadBalanceStrategy,
1300 connections: Arc<RwLock<HashMap<String, Arc<AtomicUsize>>>>,
1301 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1303 upstream: Arc<String>,
1305 policy: Option<Arc<upstream::UpstreamPolicy>>,
1307}
1308
1309impl DynamicProxy {
1310 pub(crate) fn with_trust(mut self, trust: Option<Arc<crate::proxy::UpstreamTls>>) -> Self {
1312 self.trust = trust;
1313 self
1314 }
1315
1316 pub(crate) fn with_policy(mut self, upstream: &str, policy: Option<Arc<upstream::UpstreamPolicy>>) -> Self {
1319 self.upstream = Arc::new(upstream.to_string());
1320 self.policy = policy;
1321 self
1322 }
1323
1324 pub(crate) fn new(
1325 live: Arc<RwLock<Vec<String>>>,
1326 connect_timeout_ms: u64,
1327 read_timeout_ms: u64,
1328 strip_prefix: Option<String>,
1329 add_prefix: Option<String>,
1330 tls: bool,
1331 strategy: String,
1332 ) -> Self {
1333 DynamicProxy {
1334 live,
1335 counter: Arc::new(AtomicUsize::new(0)),
1336 connect_timeout: Duration::from_millis(connect_timeout_ms),
1337 read_timeout: Duration::from_millis(read_timeout_ms),
1338 strip_prefix: strip_prefix.map(Arc::new),
1339 add_prefix: add_prefix.map(Arc::new),
1340 tls,
1341 strategy: LoadBalanceStrategy::parse(&strategy),
1342 connections: Arc::new(RwLock::new(HashMap::new())),
1343 trust: None,
1344 upstream: Arc::new(String::new()),
1345 policy: None,
1346 }
1347 }
1348
1349 fn pick(&self, candidates: &[String], client_ip: &str) -> String {
1351 let policy = self.policy.as_deref().filter(|p| p.weighted());
1352 let idx = match self.strategy {
1353 LoadBalanceStrategy::RoundRobin => match policy {
1354 Some(policy) => return policy.weighted_round_robin(candidates).unwrap_or_else(|| candidates[0].clone()),
1355 None => self.counter.fetch_add(1, Ordering::Relaxed) % candidates.len(),
1356 },
1357 LoadBalanceStrategy::Random => {
1358 let nanos = SystemTime::now()
1359 .duration_since(UNIX_EPOCH)
1360 .map(|d| d.subsec_nanos())
1361 .unwrap_or(0) as usize;
1362 let salt = self.counter.fetch_add(1, Ordering::Relaxed);
1363 let roll = nanos.wrapping_add(salt.wrapping_mul(2_654_435_761));
1364 match policy {
1365 Some(policy) => policy.weighted_index(candidates, roll),
1366 None => roll % candidates.len(),
1367 }
1368 }
1369 LoadBalanceStrategy::IpHash => {
1370 let mut hasher = DefaultHasher::new();
1371 client_ip.hash(&mut hasher);
1372 (hasher.finish() as usize) % candidates.len()
1373 }
1374 LoadBalanceStrategy::LeastConnections => {
1375 let connections = self.connections.read().unwrap();
1376 let load = |backend: &String| connections.get(backend).map(|c| c.load(Ordering::Relaxed)).unwrap_or(0) as u64;
1377 let weight = |backend: &String| self.policy.as_deref().map_or(1, |p| p.weight(backend)) as u64;
1378 candidates
1380 .iter()
1381 .enumerate()
1382 .min_by(|(_, a), (_, b)| (load(a) * weight(b)).cmp(&(load(b) * weight(a))))
1383 .map(|(i, _)| i)
1384 .unwrap_or(0)
1385 }
1386 };
1387 candidates[idx].clone()
1388 }
1389
1390 fn next_backend(&self, request: &Request, client_ip: &str, tried: &[String]) -> Result<String, bool> {
1395 let mut candidates: Vec<String> = self.live.read().unwrap().iter().filter(|b| !tried.contains(b)).cloned().collect();
1396 let Some(policy) = self.policy.as_deref() else {
1397 return if candidates.is_empty() { Err(false) } else { Ok(self.pick(&candidates, client_ip)) };
1398 };
1399 if let Some(sticky) = &policy.sticky {
1400 if let Some(id) = upstream::cookie_value(request, &sticky.cookie) {
1401 if let Some(backend) = candidates.iter().find(|b| upstream::backend_id(b) == id).cloned() {
1402 if policy.admits(&backend) {
1403 return Ok(backend);
1404 }
1405 candidates.retain(|b| *b != backend);
1406 }
1407 }
1408 }
1409 let ejected = policy.any_open(&candidates);
1410 while !candidates.is_empty() {
1411 let backend = self.pick(&candidates, client_ip);
1412 if policy.admits(&backend) {
1413 return Ok(backend);
1414 }
1415 candidates.retain(|b| *b != backend);
1416 }
1417 Err(ejected)
1418 }
1419
1420 fn connection_counter(&self, backend: &str) -> Arc<AtomicUsize> {
1423 if let Some(counter) = self.connections.read().unwrap().get(backend) {
1424 return Arc::clone(counter);
1425 }
1426 let mut connections = self.connections.write().unwrap();
1427 Arc::clone(
1428 connections
1429 .entry(backend.to_string())
1430 .or_insert_with(|| Arc::new(AtomicUsize::new(0))),
1431 )
1432 }
1433}
1434
1435struct ConnectionGuard {
1438 counter: Arc<AtomicUsize>,
1439}
1440
1441impl Drop for ConnectionGuard {
1442 fn drop(&mut self) {
1443 self.counter.fetch_sub(1, Ordering::Relaxed);
1444 }
1445}
1446
1447struct Prepared<'a> {
1450 backend: String,
1451 host: String,
1452 port: u16,
1453 request: std::borrow::Cow<'a, Request>,
1454 _guard: Option<ConnectionGuard>,
1455}
1456
1457enum Attempt {
1459 Done(Response),
1461 Failed(Option<Response>),
1464}
1465
1466impl DynamicProxy {
1467 fn prepare<'a>(&self, request: &'a Request, conn: &ConnectionInfo, tried: &[String]) -> Result<Prepared<'a>, Response> {
1472 let backend = self.next_backend(request, &conn.client.ip, tried).map_err(|ejected| if ejected { service_unavailable() } else { bad_gateway() })?;
1473
1474 let guard = if self.strategy == LoadBalanceStrategy::LeastConnections {
1475 let counter = self.connection_counter(&backend);
1476 counter.fetch_add(1, Ordering::Relaxed);
1477 Some(ConnectionGuard { counter })
1478 } else {
1479 None
1480 };
1481
1482 let (host, port, _) = crate::proxy_config::health::parse_backend_url(&backend).ok_or_else(bad_gateway)?;
1483
1484 let request = if self.strip_prefix.is_some() || self.add_prefix.is_some() {
1486 let mut req_clone = request.clone();
1487 if let Some(ref sp) = self.strip_prefix {
1488 if let Some(stripped) = req_clone.request_uri.strip_prefix(sp.as_str()) {
1489 req_clone.request_uri = if stripped.is_empty() || !stripped.starts_with('/') {
1490 format!("/{}", stripped)
1491 } else {
1492 stripped.to_string()
1493 };
1494 }
1495 }
1496 if let Some(ref ap) = self.add_prefix {
1497 req_clone.request_uri = format!("{}{}", ap, req_clone.request_uri);
1498 }
1499 std::borrow::Cow::Owned(req_clone)
1500 } else {
1501 std::borrow::Cow::Borrowed(request)
1502 };
1503
1504 Ok(Prepared { backend, host, port, request, _guard: guard })
1505 }
1506
1507 fn settle(&self, backend: &str, result: Result<Response, ()>, started: std::time::Instant) -> Attempt {
1509 let status = result.as_ref().ok().map(|r| r.status_code);
1510 upstream::observe(&self.upstream, backend, status, started);
1511 let failed = status.is_none_or(|s| matches!(s, 502..=504));
1512 if let Some(policy) = &self.policy {
1513 policy.record(backend, failed);
1514 }
1515 match result {
1516 Ok(response) if self.policy.as_deref().is_some_and(|p| p.retries_status(response.status_code)) => Attempt::Failed(Some(response)),
1517 Ok(response) => Attempt::Done(response),
1518 Err(()) => Attempt::Failed(None),
1519 }
1520 }
1521
1522 fn attempts(&self, request: &Request) -> u32 {
1524 match self.policy.as_deref().and_then(|p| p.retry.as_ref()) {
1525 Some(retry) if upstream::replayable(request) => 1 + retry.attempts,
1526 _ => 1,
1527 }
1528 }
1529
1530 fn stick(&self, request: &Request, backend: &str, response: &mut Response) {
1532 let Some(sticky) = self.policy.as_deref().and_then(|p| p.sticky.as_ref()) else { return };
1533 let id = upstream::backend_id(backend);
1534 if upstream::cookie_value(request, &sticky.cookie).as_deref() == Some(id.as_str()) {
1535 return;
1536 }
1537 let max_age = sticky.max_age_secs.map(|s| format!("; Max-Age={s}")).unwrap_or_default();
1538 response.headers.push(crate::header::Header {
1539 name: "Set-Cookie".into(),
1540 value: format!("{}={id}; Path=/; HttpOnly; SameSite=Lax{max_age}", sticky.cookie).into(),
1541 });
1542 }
1543
1544 #[cfg(feature = "http2")]
1547 pub(crate) async fn execute_async(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1548 let mut tried: Vec<String> = Vec::new();
1549 let mut last: Option<Response> = None;
1550 for attempt in 0..self.attempts(request) {
1551 let prepared = match self.prepare(request, conn, &tried) {
1552 Ok(p) => p,
1553 Err(response) => return Ok(last.unwrap_or(response)),
1554 };
1555 if attempt > 0 {
1556 upstream::observe_retry(&self.upstream);
1557 }
1558 let tls = self.tls.then(|| match &self.trust {
1559 Some(trust) => trust.async_tls(),
1560 None => crate::proxy::async_upstream::AsyncTls {
1561 config: crate::proxy::tls_client_config(),
1562 pool: crate::proxy::async_upstream::default_tls_pool(),
1563 },
1564 });
1565 let started = std::time::Instant::now();
1566 let result = crate::proxy::async_upstream::forward(
1567 &prepared.request,
1568 crate::proxy::Peer::of(conn),
1569 &prepared.host,
1570 prepared.port,
1571 tls,
1572 self.connect_timeout,
1573 self.read_timeout,
1574 )
1575 .await
1576 .map_err(|_| ());
1577 match self.settle(&prepared.backend, result, started) {
1578 Attempt::Done(mut response) => {
1579 self.stick(request, &prepared.backend, &mut response);
1580 return Ok(response);
1581 }
1582 Attempt::Failed(response) => last = response,
1583 }
1584 tried.push(prepared.backend.clone());
1585 }
1586 Ok(last.unwrap_or_else(bad_gateway))
1587 }
1588
1589 pub(crate) fn supports_async(&self) -> bool {
1591 cfg!(feature = "http2")
1592 }
1593}
1594
1595impl Application for DynamicProxy {
1596 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1597 let mut tried: Vec<String> = Vec::new();
1598 let mut last: Option<Response> = None;
1599 for attempt in 0..self.attempts(request) {
1600 let prepared = match self.prepare(request, conn, &tried) {
1601 Ok(p) => p,
1602 Err(response) => return Ok(last.unwrap_or(response)),
1603 };
1604 if attempt > 0 {
1605 upstream::observe_retry(&self.upstream);
1606 }
1607 let started = std::time::Instant::now();
1610 let result = crate::proxy::forward_upstream_trusting(
1611 crate::proxy::shared_pool(),
1612 &prepared.request,
1613 crate::proxy::Peer::of(conn),
1614 &prepared.host,
1615 prepared.port,
1616 self.tls,
1617 self.trust.as_deref(),
1618 self.connect_timeout,
1619 self.read_timeout,
1620 )
1621 .map_err(|_| ());
1622 match self.settle(&prepared.backend, result, started) {
1623 Attempt::Done(mut response) => {
1624 self.stick(request, &prepared.backend, &mut response);
1625 return Ok(response);
1626 }
1627 Attempt::Failed(response) => last = response,
1628 }
1629 tried.push(prepared.backend.clone());
1630 }
1631 Ok(last.unwrap_or_else(bad_gateway))
1632 }
1633}
1634
1635fn service_unavailable() -> Response {
1636 use crate::mime_type::MimeType;
1637 use crate::range::Range;
1638 let mut r = Response::new();
1639 r.status_code = *STATUS_CODE_REASON_PHRASE.n503_service_unavailable.status_code;
1640 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n503_service_unavailable.reason_phrase.to_string();
1641 r.headers.push(crate::header::Header { name: "Retry-After".into(), value: "5".into() });
1642 r.content_range_list = vec![Range::get_content_range(b"503 Service Unavailable: every backend is out of rotation".to_vec(), MimeType::TEXT_PLAIN.to_string())];
1643 r
1644}
1645
1646fn bad_gateway() -> Response {
1647 use crate::mime_type::MimeType;
1648 use crate::range::Range;
1649 let cr = Range::get_content_range(
1650 b"502 Bad Gateway".to_vec(),
1651 MimeType::TEXT_PLAIN.to_string(),
1652 );
1653 let mut r = Response::new();
1654 r.status_code = *STATUS_CODE_REASON_PHRASE.n502_bad_gateway.status_code;
1655 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n502_bad_gateway.reason_phrase.to_string();
1656 r.content_range_list = vec![cr];
1657 r
1658}
1659
1660#[derive(Clone)]
1666pub(crate) struct RedirectAdapter {
1667 location_template: Arc<String>,
1668 status: i16,
1669 reason: Arc<String>,
1670}
1671
1672impl RedirectAdapter {
1673 pub(crate) fn new(location: String, status: u16) -> Self {
1674 let (code, reason) = redirect_status(status);
1675 RedirectAdapter {
1676 location_template: Arc::new(location),
1677 status: code,
1678 reason: Arc::new(reason),
1679 }
1680 }
1681}
1682
1683fn redirect_status(code: u16) -> (i16, String) {
1684 let phrase = match code {
1685 301 => STATUS_CODE_REASON_PHRASE.n301_moved_permanently.reason_phrase,
1686 302 => STATUS_CODE_REASON_PHRASE.n302_found.reason_phrase,
1687 307 => STATUS_CODE_REASON_PHRASE.n307_temporary_redirect.reason_phrase,
1688 308 => STATUS_CODE_REASON_PHRASE.n308_permanent_redirect.reason_phrase,
1689 _ => "Redirect",
1690 };
1691 (code as i16, phrase.to_string())
1692}
1693
1694impl Application for RedirectAdapter {
1695 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1696 let location = self
1697 .location_template
1698 .replace("$path", &request.request_uri);
1699 use crate::header::Header;
1700 let mut r = Response::new();
1701 r.status_code = self.status;
1702 r.reason_phrase = self.reason.as_ref().clone();
1703 r.headers.push(Header { name: "Location".into(), value: location.into() });
1704 Ok(r)
1705 }
1706}
1707
1708#[derive(Clone)]
1712pub(crate) struct RespondAdapter {
1713 status: i16,
1714 reason: Arc<String>,
1715 body: Arc<Vec<u8>>,
1716 content_type: Arc<String>,
1717}
1718
1719impl RespondAdapter {
1720 pub(crate) fn new(status: u16, body: String, content_type: String) -> Self {
1721 use crate::response::STATUS_CODE_REASON_PHRASE;
1722 let reason = match status {
1723 200 => STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
1724 201 => STATUS_CODE_REASON_PHRASE.n201_created.reason_phrase.to_string(),
1725 204 => STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string(),
1726 400 => STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string(),
1727 401 => STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string(),
1728 403 => STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string(),
1729 404 => STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string(),
1730 500 => STATUS_CODE_REASON_PHRASE.n500_internal_server_error.reason_phrase.to_string(),
1731 _ => "OK".to_string(),
1732 };
1733 RespondAdapter {
1734 status: status as i16,
1735 reason: Arc::new(reason),
1736 body: Arc::new(body.into_bytes()),
1737 content_type: Arc::new(content_type),
1738 }
1739 }
1740}
1741
1742impl Application for RespondAdapter {
1743 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1744 use crate::range::Range;
1745 let mut r = Response::new();
1746 r.status_code = self.status;
1747 r.reason_phrase = self.reason.as_ref().clone();
1748 if !self.body.is_empty() {
1749 r.content_range_list = vec![Range::get_content_range(
1750 self.body.as_ref().clone(),
1751 self.content_type.as_ref().clone(),
1752 )];
1753 }
1754 Ok(r)
1755 }
1756}
1757
1758#[derive(Clone)]
1767pub(crate) struct StaticAdapter {
1768 root: Arc<std::path::PathBuf>,
1769 index: Arc<Vec<String>>,
1770}
1771
1772impl StaticAdapter {
1773 pub(crate) fn new(root: String, index: Vec<String>) -> Self {
1774 let index = if index.is_empty() { vec!["index.html".to_string()] } else { index };
1775 StaticAdapter {
1776 root: Arc::new(std::path::PathBuf::from(root)),
1777 index: Arc::new(index),
1778 }
1779 }
1780
1781 fn resolve(&self, request_uri: &str) -> Option<std::path::PathBuf> {
1785 let raw_path = request_uri.split('?').next().unwrap_or(request_uri);
1786 let decoded = crate::url::URL::percent_decode(raw_path);
1787
1788 if decoded.split('/').any(|segment| segment == "..") {
1789 return None;
1790 }
1791
1792 let relative = decoded.trim_start_matches('/');
1793 Some(self.root.join(relative))
1794 }
1795}
1796
1797impl Application for StaticAdapter {
1798 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1799 let mut response = Response::new();
1800
1801 let not_found = |mut response: Response| {
1802 response.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1803 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1804 response
1805 };
1806
1807 let candidate = match self.resolve(&request.request_uri) {
1808 Some(p) => p,
1809 None => {
1810 response.status_code = *STATUS_CODE_REASON_PHRASE.n403_forbidden.status_code;
1811 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string();
1812 return Ok(response);
1813 }
1814 };
1815
1816 let mut file_path = candidate;
1817 if file_path.is_dir() {
1818 let indexed = self
1819 .index
1820 .iter()
1821 .map(|name| file_path.join(name))
1822 .find(|p| p.is_file());
1823
1824 file_path = match indexed {
1825 Some(p) => p,
1826 None => return Ok(not_found(response)),
1827 };
1828 }
1829
1830 if !file_path.is_file() {
1831 return Ok(not_found(response));
1832 }
1833
1834 if let (Ok(root_canon), Ok(file_canon)) =
1837 (self.root.canonicalize(), file_path.canonicalize())
1838 {
1839 if !file_canon.starts_with(&root_canon) {
1840 return Ok(not_found(response));
1841 }
1842 }
1843
1844 let path_str = match file_path.to_str() {
1845 Some(s) => s,
1846 None => return Ok(not_found(response)),
1847 };
1848
1849 match crate::range::Range::get_content_range_of_a_file(path_str) {
1850 Ok(content_range) => {
1851 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
1852 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
1853 response.content_range_list = vec![content_range];
1854 Ok(response)
1855 }
1856 Err(_) => Ok(not_found(response)),
1857 }
1858 }
1859}
1860
1861pub(crate) struct PerRouteRateLimit(pub(crate) Arc<crate::rate_limit::RateLimiter>);
1865
1866impl crate::middleware::Middleware for PerRouteRateLimit {
1867 fn handle(
1868 &self,
1869 request: &Request,
1870 conn: &ConnectionInfo,
1871 next: &dyn Application,
1872 ) -> Result<Response, String> {
1873 use crate::error::{AppError, IntoResponse};
1874 if self.0.check(&conn.client.ip) {
1875 next.execute(request, conn)
1876 } else {
1877 let mut response = AppError::TooManyRequests.into_response();
1878 let retry_after = self.0.retry_after(&conn.client.ip);
1879 response.headers.push(Header {
1880 name: Header::_RETRY_AFTER.into(),
1881 value: retry_after.to_string().into(),
1882 });
1883 Ok(response)
1884 }
1885 }
1886}
1887
1888pub(crate) struct PerRouteMaxBodySize(pub(crate) u64);
1903
1904impl crate::middleware::Middleware for PerRouteMaxBodySize {
1905 fn handle(
1906 &self,
1907 request: &Request,
1908 conn: &ConnectionInfo,
1909 next: &dyn Application,
1910 ) -> Result<Response, String> {
1911 use crate::error::{AppError, IntoResponse};
1912 if request.body.len() as u64 > self.0 {
1913 Ok(AppError::PayloadTooLarge(format!(
1914 "request body of {} bytes exceeds this route's {}-byte limit",
1915 request.body.len(),
1916 self.0,
1917 ))
1918 .into_response())
1919 } else {
1920 next.execute(request, conn)
1921 }
1922 }
1923}
1924
1925pub(crate) struct BearerAuthMiddleware {
1929 pub(crate) token: Arc<String>,
1930}
1931
1932impl crate::middleware::Middleware for BearerAuthMiddleware {
1933 fn handle(
1934 &self,
1935 request: &Request,
1936 conn: &ConnectionInfo,
1937 next: &dyn Application,
1938 ) -> Result<Response, String> {
1939 use crate::error::{AppError, IntoResponse};
1940 let expected = format!("Bearer {}", self.token);
1941 let authorized = request
1942 .headers
1943 .iter()
1944 .any(|h| h.name.eq_ignore_ascii_case("authorization") && constant_time_eq(h.value.as_bytes(), expected.as_bytes()));
1945 if authorized {
1946 next.execute(request, conn)
1947 } else {
1948 Ok(AppError::Unauthorized.into_response())
1949 }
1950 }
1951}
1952
1953pub(crate) fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
1956 if a.len() != b.len() {
1957 return false;
1958 }
1959 a.iter().zip(b).fold(0u8, |diff, (x, y)| diff | (x ^ y)) == 0
1960}
1961
1962pub(crate) struct AuthMisconfigured;
1965
1966impl crate::middleware::Middleware for AuthMisconfigured {
1967 fn handle(
1968 &self,
1969 _request: &Request,
1970 _conn: &ConnectionInfo,
1971 _next: &dyn Application,
1972 ) -> Result<Response, String> {
1973 Ok(Response::text(
1974 STATUS_CODE_REASON_PHRASE.n503_service_unavailable,
1975 "503 Service Unavailable: authentication for this route is misconfigured",
1976 ))
1977 }
1978}
1979
1980pub(crate) fn arc_app<A: Application + Send + Sync + 'static>(
1984 a: A,
1985) -> Arc<dyn Application + Send + Sync> {
1986 Arc::new(a)
1987}
1988
1989pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
1994 builder::build_from_file()
1995}