1pub mod parser;
29pub mod health;
30pub mod builder;
31pub mod validate;
32
33#[cfg(test)]
34mod tests;
35
36use std::sync::atomic::{AtomicBool, Ordering};
37use std::sync::{Arc, RwLock};
38
39use crate::app::App;
40use crate::application::Application;
41use crate::core::New;
42use crate::request::Request;
43use crate::header::Header;
44use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
45use crate::server::ConnectionInfo;
46use crate::server_config::ServerConfig;
47
48#[derive(Debug, Clone)]
51pub struct ProxyConfig {
52 pub upstreams: Vec<UpstreamConfig>,
53 pub routes: Vec<RouteConfig>,
54 pub tcp_proxies: Vec<TcpProxyConfig>,
55 pub udp_proxies: Vec<UdpProxyConfig>,
56 pub ws_proxies: Vec<WsProxyConfig>,
57 pub global_middleware: MiddlewareConfig,
58}
59
60#[derive(Debug, Clone)]
61pub struct UpstreamConfig {
62 pub name: String,
63 pub backends: Vec<String>,
64 pub strategy: String, pub health_check: Option<HealthCheckConfig>,
66 pub tls: bool,
70 pub tls_ca_file: Option<String>,
74 pub tls_client_cert_file: Option<String>,
78 pub tls_client_key_file: Option<String>,
79}
80
81#[derive(Debug, Clone)]
82pub struct HealthCheckConfig {
83 pub path: String,
84 pub interval_secs: u64,
85 pub timeout_ms: u64,
86 pub healthy_threshold: u32,
87 pub unhealthy_threshold: u32,
88}
89
90#[derive(Debug, Clone)]
91pub struct RouteConfig {
92 pub name: String,
93 pub match_: MatchConfig,
94 pub action: ActionConfig,
95 pub middleware: MiddlewareConfig,
96}
97
98#[derive(Debug, Clone, Default)]
99pub struct MatchConfig {
100 pub host: Option<String>,
101 pub path: Option<String>,
102 pub method: Option<String>,
103 pub content_type: Option<String>,
104}
105
106#[derive(Debug, Clone)]
107pub enum ActionConfig {
108 Proxy {
109 upstream: String,
110 connect_timeout_ms: u64,
111 read_timeout_ms: u64,
112 strip_path_prefix: Option<String>,
113 add_path_prefix: Option<String>,
114 },
115 Grpc {
116 upstream: String,
117 connect_timeout_ms: u64,
118 read_timeout_ms: u64,
119 },
120 Static {
121 root: String,
122 index: Vec<String>,
123 },
124 Redirect {
125 location: String,
126 status: u16,
127 },
128 Respond {
129 status: u16,
130 body: String,
131 content_type: String,
132 },
133 Mcp,
134 Unknown(String),
135}
136
137#[derive(Debug, Clone, Default)]
138pub struct MiddlewareConfig {
139 pub rate_limit: Option<RateLimitConfig>,
140 pub cache: Option<CacheConfig>,
141 pub auth: Option<AuthConfig>,
142 pub rewrite_request: Vec<RewriteRuleConfig>,
143 pub rewrite_response: Vec<RewriteRuleConfig>,
144 pub ip_allow: Vec<String>,
145 pub ip_deny: Vec<String>,
146 pub timeout_ms: Option<u64>,
151 pub max_body_size: Option<u64>,
167}
168
169#[derive(Debug, Clone)]
170pub struct RateLimitConfig {
171 pub max_requests: u32,
172 pub window_secs: u64,
173}
174
175#[derive(Debug, Clone)]
176pub struct CacheConfig {
177 pub ttl_secs: u64,
178 pub vary_by: Vec<String>,
179}
180
181#[derive(Debug, Clone)]
182pub enum AuthConfig {
183 Basic { htpasswd_file: String },
188 Jwt { secret_env: String },
191 Bearer { token_env: String },
192}
193
194#[derive(Debug, Clone, Default)]
195pub struct RewriteRuleConfig {
196 pub type_: String,
197 pub name: Option<String>,
198 pub value: Option<String>,
199 pub prefix: Option<String>,
200 pub from: Option<String>,
201 pub to: Option<String>,
202 pub code: Option<u16>,
203 pub reason: Option<String>,
204}
205
206#[derive(Debug, Clone)]
207pub struct TcpProxyConfig {
208 pub name: String,
209 pub listen: String,
210 pub backends: Vec<String>,
211 pub connect_timeout_ms: u64,
212}
213
214#[derive(Debug, Clone)]
215pub struct UdpProxyConfig {
216 pub name: String,
217 pub listen: String,
218 pub backends: Vec<String>,
219 pub reply_timeout_ms: u64,
220 pub buffer_size: usize,
221}
222
223#[derive(Debug, Clone)]
224pub struct WsProxyConfig {
225 pub name: String,
226 pub listen: String,
227 pub backends: Vec<String>,
228 pub connect_timeout_ms: u64,
229 pub read_timeout_ms: u64,
230 pub health_check: Option<HealthCheckConfig>,
237}
238
239fn merge_action_keys(map: &mut parser::SectionMap, action_section: &str, action_type: &str) {
248 if action_type.is_empty() {
249 return;
250 }
251 let short_form: Vec<(String, String)> = match map.get(action_section) {
252 Some(entries) => entries.iter().filter(|(k, _)| k != "type").cloned().collect(),
253 None => return,
254 };
255 let nested = map.entry(format!("{action_section}.{action_type}")).or_default();
256 for (key, value) in short_form {
257 if !nested.iter().any(|(k, _)| *k == key) {
258 nested.push((key, value));
259 }
260 }
261}
262
263impl ProxyConfig {
264 pub fn is_proxy_mode() -> bool {
268 let path = config_file_path();
269 match std::fs::read_to_string(&path) {
270 Ok(contents) => {
271 contents.contains("[[route]]") || contents.contains("[[upstream]]")
272 }
273 Err(_) => false,
274 }
275 }
276
277 pub fn load() -> Self {
279 let path = config_file_path();
280 let contents = std::fs::read_to_string(&path).unwrap_or_default();
281 Self::from_str(&contents)
282 }
283
284 pub fn from_str(toml: &str) -> Self {
286 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
287
288 let mut map = parser::parse(toml);
289
290 let mut upstreams = Vec::new();
292 let mut i = 0;
293 loop {
294 let sec = format!("upstream[{}]", i);
295 if !section_exists(&map, &sec) {
296 break;
297 }
298 let name = get_str(&map, &sec, "name");
299 let backends = get_array(&map, &sec, "backends");
300 let strategy = {
301 let s = get_str(&map, &sec, "strategy");
302 if s.is_empty() { "round_robin".to_string() } else { s }
303 };
304 let hc_sec = format!("{}.health_check", sec);
305 let health_check = if section_exists(&map, &hc_sec) {
306 Some(HealthCheckConfig {
307 path: {
308 let p = get_str(&map, &hc_sec, "path");
309 if p.is_empty() { "/health".to_string() } else { p }
310 },
311 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
312 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
313 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
314 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
315 })
316 } else {
317 None
318 };
319 let tls = backends.iter().any(|b| ["https://", "h2s://", "grpcs://"].iter().any(|s| b.starts_with(s)));
320 let tls_ca_file = Some(get_str(&map, &sec, "tls_ca_file")).filter(|v| !v.is_empty());
321 let tls_client_cert_file = Some(get_str(&map, &sec, "tls_client_cert_file")).filter(|v| !v.is_empty());
322 let tls_client_key_file = Some(get_str(&map, &sec, "tls_client_key_file")).filter(|v| !v.is_empty());
323 upstreams.push(UpstreamConfig {
324 name, backends, strategy, health_check, tls, tls_ca_file, tls_client_cert_file, tls_client_key_file,
325 });
326 i += 1;
327 }
328
329 let mut routes = Vec::new();
331 let mut i = 0;
332 loop {
333 let sec = format!("route[{}]", i);
334 if !section_exists(&map, &sec) {
335 break;
336 }
337 let name = get_str(&map, &sec, "name");
338
339 let m_sec = format!("{}.match", sec);
341 let match_ = MatchConfig {
342 host: {
343 let h = get_str(&map, &m_sec, "host");
344 if h.is_empty() { None } else { Some(h) }
345 },
346 path: {
347 let p = get_str(&map, &m_sec, "path");
348 if p.is_empty() { None } else { Some(p) }
349 },
350 method: {
351 let m = get_str(&map, &m_sec, "method");
352 if m.is_empty() { None } else { Some(m.to_uppercase()) }
353 },
354 content_type: {
355 let c = get_str(&map, &m_sec, "content_type");
356 if c.is_empty() { None } else { Some(c) }
357 },
358 };
359
360 let a_sec = format!("{}.action", sec);
362 let action_type = get_str(&map, &a_sec, "type");
363 merge_action_keys(&mut map, &a_sec, &action_type);
364 let action = match action_type.as_str() {
365 "proxy" => {
366 let p_sec = format!("{}.action.proxy", sec);
367 ActionConfig::Proxy {
368 upstream: get_str(&map, &p_sec, "upstream"),
369 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
370 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
371 strip_path_prefix: {
372 let v = get_str(&map, &p_sec, "strip_path_prefix");
373 if v.is_empty() { None } else { Some(v) }
374 },
375 add_path_prefix: {
376 let v = get_str(&map, &p_sec, "add_path_prefix");
377 if v.is_empty() { None } else { Some(v) }
378 },
379 }
380 }
381 "grpc" => {
382 let p_sec = format!("{}.action.grpc", sec);
383 ActionConfig::Grpc {
384 upstream: get_str(&map, &p_sec, "upstream"),
385 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
386 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
387 }
388 }
389 "static" => {
390 let s_sec = format!("{}.action.static", sec);
391 ActionConfig::Static {
392 root: get_str(&map, &s_sec, "root"),
393 index: get_array(&map, &s_sec, "index"),
394 }
395 }
396 "redirect" => {
397 let r_sec = format!("{}.action.redirect", sec);
398 ActionConfig::Redirect {
399 location: get_str(&map, &r_sec, "location"),
400 status: get_u64(&map, &r_sec, "status", 301) as u16,
401 }
402 }
403 "respond" => {
404 let r_sec = format!("{}.action.respond", sec);
405 ActionConfig::Respond {
406 status: get_u64(&map, &r_sec, "status", 200) as u16,
407 body: get_str(&map, &r_sec, "body"),
408 content_type: {
409 let ct = get_str(&map, &r_sec, "content_type");
410 if ct.is_empty() { "text/plain".to_string() } else { ct }
411 },
412 }
413 }
414 "mcp" => ActionConfig::Mcp,
415 other => ActionConfig::Unknown(other.to_string()),
416 };
417
418 let mw_sec = format!("{}.middleware", sec);
420 let middleware = parse_middleware_config(&map, &mw_sec, i);
421
422 routes.push(RouteConfig { name, match_, action, middleware });
423 i += 1;
424 }
425
426 let mut tcp_proxies = Vec::new();
428 let mut i = 0;
429 loop {
430 let sec = format!("tcp_proxy[{}]", i);
431 if !section_exists(&map, &sec) {
432 break;
433 }
434 tcp_proxies.push(TcpProxyConfig {
435 name: get_str(&map, &sec, "name"),
436 listen: get_str(&map, &sec, "listen"),
437 backends: get_array(&map, &sec, "backends"),
438 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
439 });
440 i += 1;
441 }
442
443 let mut udp_proxies = Vec::new();
445 let mut i = 0;
446 loop {
447 let sec = format!("udp_proxy[{}]", i);
448 if !section_exists(&map, &sec) {
449 break;
450 }
451 udp_proxies.push(UdpProxyConfig {
452 name: get_str(&map, &sec, "name"),
453 listen: get_str(&map, &sec, "listen"),
454 backends: get_array(&map, &sec, "backends"),
455 reply_timeout_ms: get_u64(&map, &sec, "reply_timeout_ms", 5000),
456 buffer_size: get_u64(&map, &sec, "buffer_size", 65536) as usize,
457 });
458 i += 1;
459 }
460
461 let mut ws_proxies = Vec::new();
463 let mut i = 0;
464 loop {
465 let sec = format!("ws_proxy[{}]", i);
466 if !section_exists(&map, &sec) {
467 break;
468 }
469 let hc_sec = format!("{}.health_check", sec);
470 let health_check = if section_exists(&map, &hc_sec) {
471 Some(HealthCheckConfig {
472 path: {
473 let p = get_str(&map, &hc_sec, "path");
474 if p.is_empty() { "/health".to_string() } else { p }
475 },
476 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
477 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
478 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
479 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
480 })
481 } else {
482 None
483 };
484 ws_proxies.push(WsProxyConfig {
485 name: get_str(&map, &sec, "name"),
486 listen: get_str(&map, &sec, "listen"),
487 backends: get_array(&map, &sec, "backends"),
488 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
489 read_timeout_ms: get_u64(&map, &sec, "read_timeout_ms", 30000),
490 health_check,
491 });
492 i += 1;
493 }
494
495 let global_middleware = parse_middleware_config(&map, "middleware", usize::MAX);
497
498 ProxyConfig {
499 upstreams,
500 routes,
501 tcp_proxies,
502 udp_proxies,
503 ws_proxies,
504 global_middleware,
505 }
506 }
507}
508
509fn parse_middleware_config(
512 map: &parser::SectionMap,
513 mw_sec: &str,
514 route_idx: usize,
515) -> MiddlewareConfig {
516 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
517
518 let rl_sec = format!("{}.rate_limit", mw_sec);
519 let rate_limit = if section_exists(map, &rl_sec) {
520 Some(RateLimitConfig {
521 max_requests: get_u32(map, &rl_sec, "max_requests", 1000),
522 window_secs: get_u64(map, &rl_sec, "window_secs", 60),
523 })
524 } else {
525 None
526 };
527
528 let c_sec = format!("{}.cache", mw_sec);
529 let cache = if section_exists(map, &c_sec) {
530 Some(CacheConfig {
531 ttl_secs: get_u64(map, &c_sec, "ttl_secs", 60),
532 vary_by: get_array(map, &c_sec, "vary_by"),
533 })
534 } else {
535 None
536 };
537
538 let a_sec = format!("{}.auth", mw_sec);
539 let auth = if section_exists(map, &a_sec) {
540 let auth_type = get_str(map, &a_sec, "type");
541 match auth_type.as_str() {
542 "bearer" => Some(AuthConfig::Bearer {
543 token_env: get_str(map, &a_sec, "token_env"),
544 }),
545 "jwt" => Some(AuthConfig::Jwt {
546 secret_env: get_str(map, &a_sec, "secret_env"),
547 }),
548 "basic" => Some(AuthConfig::Basic {
549 htpasswd_file: get_str(map, &a_sec, "htpasswd_file"),
550 }),
551 _ => None,
552 }
553 } else {
554 None
555 };
556
557 let rewrite_request = collect_rewrite_rules(map, mw_sec, "request");
563 let rewrite_response = collect_rewrite_rules(map, mw_sec, "response");
564
565 let ip_sec = format!("{}.ip_filter", mw_sec);
566 let ip_allow = if section_exists(map, &ip_sec) {
567 get_array(map, &ip_sec, "allow")
568 } else {
569 vec![]
570 };
571 let ip_deny = if section_exists(map, &ip_sec) {
572 get_array(map, &ip_sec, "deny")
573 } else {
574 vec![]
575 };
576
577 let _ = route_idx; let timeout_ms = match get_u64(map, mw_sec, "timeout_ms", 0) {
583 0 => None,
584 ms => Some(ms),
585 };
586
587 let max_body_size = match get_u64(map, mw_sec, "max_body_size", 0) {
591 0 => None,
592 n => Some(n),
593 };
594
595 MiddlewareConfig {
596 rate_limit, cache, auth, rewrite_request, rewrite_response, ip_allow, ip_deny,
597 timeout_ms, max_body_size,
598 }
599}
600
601fn collect_rewrite_rules(
603 map: &parser::SectionMap,
604 mw_sec: &str,
605 direction: &str,
606) -> Vec<RewriteRuleConfig> {
607 use parser::{get_str, get_u64};
608
609 let mut rules = Vec::new();
610 let mut j = 0;
611 loop {
612 let rsec = format!("{}.rewrite.{}[{}]", mw_sec, direction, j);
613 if !parser::section_exists(map, &rsec) {
614 break;
615 }
616 let code_val = get_u64(map, &rsec, "code", 0);
617 rules.push(RewriteRuleConfig {
618 type_: get_str(map, &rsec, "type"),
619 name: {
620 let v = get_str(map, &rsec, "name");
621 if v.is_empty() { None } else { Some(v) }
622 },
623 value: {
624 let v = get_str(map, &rsec, "value");
625 if v.is_empty() { None } else { Some(v) }
626 },
627 prefix: {
628 let v = get_str(map, &rsec, "prefix");
629 if v.is_empty() { None } else { Some(v) }
630 },
631 from: {
632 let v = get_str(map, &rsec, "from");
633 if v.is_empty() { None } else { Some(v) }
634 },
635 to: {
636 let v = get_str(map, &rsec, "to");
637 if v.is_empty() { None } else { Some(v) }
638 },
639 code: if code_val == 0 { None } else { Some(code_val as u16) },
640 reason: {
641 let v = get_str(map, &rsec, "reason");
642 if v.is_empty() { None } else { Some(v) }
643 },
644 });
645 j += 1;
646 }
647 rules
648}
649
650pub(crate) fn config_file_path() -> String {
651 std::env::var("RWS_CONFIG_FILE").unwrap_or_else(|_| "rws.config.toml".to_string())
652}
653
654pub(crate) struct CompiledRoute {
658 pub(crate) matcher: RouteMatcher,
659 pub(crate) handler: Arc<dyn Application + Send + Sync>,
661 #[cfg_attr(not(feature = "http2"), allow(dead_code))]
665 pub(crate) async_proxy: Option<Arc<DynamicProxy>>,
666}
667
668#[derive(Clone, Default)]
670pub(crate) struct RouteMatcher {
671 pub(crate) host: Option<String>,
673 pub(crate) path_prefix: Option<String>,
675 pub(crate) path_exact: Option<String>,
677 pub(crate) method: Option<String>,
679 pub(crate) content_type_prefix: Option<String>,
681}
682
683impl RouteMatcher {
684 pub(crate) fn from_match_config(cfg: &MatchConfig) -> Self {
685 let (path_prefix, path_exact) = match &cfg.path {
686 Some(p) if p.ends_with('*') => {
687 let stripped = p.trim_end_matches('*').to_string();
689 (Some(stripped), None)
690 }
691 Some(p) => (None, Some(p.clone())),
692 None => (None, None),
693 };
694 let content_type_prefix = cfg.content_type.as_ref().map(|ct| {
695 if ct.ends_with('*') {
696 ct.trim_end_matches('*').to_string()
697 } else {
698 ct.clone()
699 }
700 });
701 RouteMatcher {
702 host: cfg.host.clone(),
703 path_prefix,
704 path_exact,
705 method: cfg.method.clone(),
706 content_type_prefix,
707 }
708 }
709
710 pub(crate) fn matches(&self, request: &Request, conn: &ConnectionInfo) -> bool {
712 if let Some(ref expected_host) = self.host {
714 let actual_host = conn
715 .sni_hostname
716 .as_deref()
717 .or_else(|| {
718 request
719 .headers
720 .iter()
721 .find(|h| h.name.eq_ignore_ascii_case("host"))
722 .map(|h| h.value.as_ref())
723 })
724 .unwrap_or("");
725 if !host_matches(expected_host, actual_host) {
726 return false;
727 }
728 }
729
730 if let Some(ref m) = self.method {
732 if request.method.to_uppercase() != m.as_str() {
733 return false;
734 }
735 }
736
737 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
739 if let Some(ref prefix) = self.path_prefix {
740 if !path.starts_with(prefix.as_str()) {
741 return false;
742 }
743 } else if let Some(ref exact) = self.path_exact {
744 if path != exact.as_str() {
745 return false;
746 }
747 }
748
749 if let Some(ref ct_prefix) = self.content_type_prefix {
751 let actual_ct = request
752 .headers
753 .iter()
754 .find(|h| h.name.eq_ignore_ascii_case("content-type"))
755 .map(|h| h.value.as_ref())
756 .unwrap_or("");
757 if !actual_ct.starts_with(ct_prefix.as_str()) {
758 return false;
759 }
760 }
761
762 true
763 }
764}
765
766#[derive(Clone)]
773pub struct ConfigDrivenApp {
774 current: Arc<RwLock<Arc<Generation>>>,
775 reload_lock: Arc<std::sync::Mutex<()>>,
777}
778
779struct Generation {
781 dispatch: RouteDispatch,
782 entry: Option<Arc<dyn Application + Send + Sync>>,
785 config: ProxyConfig,
786 source: Option<String>,
788 health_stop: Arc<AtomicBool>,
790}
791
792impl Generation {
793 fn new(routes: Arc<Vec<CompiledRoute>>, fallback: App, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
794 let dispatch = RouteDispatch { routes, fallback };
795 let entry = if config.global_middleware.is_empty() {
796 None
797 } else {
798 Some(builder::apply_middleware(arc_app(dispatch.clone()), &config.global_middleware))
799 };
800 Generation { dispatch, entry, config, source, health_stop }
801 }
802}
803
804#[derive(Clone)]
806struct RouteDispatch {
807 routes: Arc<Vec<CompiledRoute>>,
808 fallback: App,
813}
814
815impl Application for RouteDispatch {
816 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
817 for route in self.routes.iter() {
818 if route.matcher.matches(request, conn) {
819 return route.handler.execute(request, conn);
820 }
821 }
822 self.fallback.execute(request, conn)
823 }
824
825 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
826 match self.routes.iter().find(|r| r.matcher.matches(request, conn)) {
827 Some(route) => route.handler.streams_body(request, conn),
828 None => self.fallback.streams_body(request, conn),
829 }
830 }
831}
832
833impl MiddlewareConfig {
834 pub fn is_empty(&self) -> bool {
836 self.rate_limit.is_none() && self.cache.is_none() && self.auth.is_none()
837 && self.rewrite_request.is_empty() && self.rewrite_response.is_empty()
838 && self.ip_allow.is_empty() && self.ip_deny.is_empty()
839 && self.timeout_ms.is_none() && self.max_body_size.is_none()
840 }
841}
842
843#[derive(Debug, Clone, PartialEq)]
845pub enum ReloadOutcome {
846 Unchanged,
848 Reloaded { routes: usize, warnings: Vec<String> },
851}
852
853impl ConfigDrivenApp {
854 pub(crate) fn new(routes: Vec<CompiledRoute>, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
855 use crate::core::New;
856 let generation = Generation::new(Arc::new(routes), App::new(), config, health_stop, source);
857 ConfigDrivenApp {
858 current: Arc::new(RwLock::new(Arc::new(generation))),
859 reload_lock: Arc::new(std::sync::Mutex::new(())),
860 }
861 }
862
863 fn current(&self) -> Arc<Generation> {
864 Arc::clone(&self.current.read().unwrap_or_else(|e| e.into_inner()))
865 }
866
867 pub fn with_config(self, config: ServerConfig) -> Self {
884 {
885 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
886 let old = Arc::clone(&slot);
887 *slot = Arc::new(Generation::new(
888 Arc::clone(&old.dispatch.routes),
889 App::with_config(config),
890 old.config.clone(),
891 Arc::clone(&old.health_stop),
892 old.source.clone(),
893 ));
894 }
895 self
896 }
897
898 pub fn reload_from_str(&self, toml: &str) -> Result<ReloadOutcome, crate::config_check::ConfigReport> {
908 let _serialized = self.reload_lock.lock().unwrap_or_else(|e| e.into_inner());
909 let old = self.current();
910 if old.source.as_deref() == Some(toml) {
911 return Ok(ReloadOutcome::Unchanged);
912 }
913 let config = ProxyConfig::from_str(toml);
914 let mut report = crate::config_check::ConfigReport::default();
915 validate::check_unknown_keys(toml, &mut report);
916 config.validate(&mut report);
917 if !report.is_ok() {
918 return Err(report);
919 }
920
921 let mut warnings = report.warnings;
922 let listeners = |c: &ProxyConfig| format!("{:?}{:?}{:?}", c.tcp_proxies, c.udp_proxies, c.ws_proxies);
923 if listeners(&config) != listeners(&old.config) {
924 warnings.push("[[tcp_proxy]] / [[udp_proxy]] / [[ws_proxy]] changes need a restart; the old listeners keep running".to_string());
925 }
926
927 let health_stop = Arc::new(AtomicBool::new(false));
928 let routes = builder::compile_routes(&config, &health_stop);
929 let route_count = routes.len();
930 let generation = Generation::new(Arc::new(routes), old.dispatch.fallback.clone(), config, health_stop, Some(toml.to_string()));
931 let replaced = {
932 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
933 std::mem::replace(&mut *slot, Arc::new(generation))
934 };
935 replaced.health_stop.store(true, Ordering::Relaxed);
936 Ok(ReloadOutcome::Reloaded { routes: route_count, warnings })
937 }
938
939 pub fn reload_from_file(&self) {
944 let path = config_file_path();
945 let toml = match std::fs::read_to_string(&path) {
946 Ok(toml) => toml,
947 Err(e) => {
948 eprintln!("[proxy_config] reload: cannot read {path}: {e} — keeping the running routes");
949 return;
950 }
951 };
952 match self.reload_from_str(&toml) {
953 Ok(ReloadOutcome::Unchanged) => {}
954 Ok(ReloadOutcome::Reloaded { routes, warnings }) => {
955 for w in &warnings {
956 eprintln!("[proxy_config] Warning: {w}");
957 }
958 println!("[proxy_config] reloaded {path}: {routes} route{}", if routes == 1 { "" } else { "s" });
959 }
960 Err(report) => {
961 for e in &report.errors {
962 eprintln!("[proxy_config] reload ERROR: {e}");
963 }
964 eprintln!("[proxy_config] {path} has errors — keeping the running routes");
965 }
966 }
967 }
968}
969
970fn normalized(request: &Request) -> std::borrow::Cow<'_, Request> {
973 match normalize_request_target(&request.request_uri) {
974 std::borrow::Cow::Borrowed(_) => std::borrow::Cow::Borrowed(request),
975 std::borrow::Cow::Owned(target) => {
976 let mut owned = request.clone();
977 owned.request_uri = target;
978 std::borrow::Cow::Owned(owned)
979 }
980 }
981}
982
983pub(crate) fn normalize_request_target(target: &str) -> std::borrow::Cow<'_, str> {
991 let (path, query) = match target.find('?') {
992 Some(i) => (&target[..i], &target[i..]),
993 None => (target, ""),
994 };
995 if !path.starts_with('/') || !(path.contains('%') || path.contains("//") || path.contains("/.")) {
996 return std::borrow::Cow::Borrowed(target);
997 }
998 let bytes = path.as_bytes();
999 let mut decoded = String::with_capacity(path.len());
1000 let mut i = 0;
1001 while i < bytes.len() {
1002 let hex = |b: u8| (b as char).to_digit(16);
1003 if bytes[i] == b'%' && i + 2 < bytes.len() {
1004 if let (Some(h), Some(l)) = (hex(bytes[i + 1]), hex(bytes[i + 2])) {
1005 let value = (h * 16 + l) as u8;
1006 if value.is_ascii_alphanumeric() || matches!(value, b'-' | b'.' | b'_' | b'~') {
1007 decoded.push(value as char);
1008 } else {
1009 decoded.push('%');
1010 decoded.push(bytes[i + 1].to_ascii_uppercase() as char);
1011 decoded.push(bytes[i + 2].to_ascii_uppercase() as char);
1012 }
1013 i += 3;
1014 continue;
1015 }
1016 }
1017 if bytes[i] == b'%' {
1021 decoded.push_str("%25");
1022 i += 1;
1023 continue;
1024 }
1025 let ch = path[i..].chars().next().unwrap_or('\u{FFFD}');
1027 decoded.push(ch);
1028 i += ch.len_utf8().max(1);
1029 }
1030 let mut segments: Vec<&str> = Vec::new();
1031 let parts: Vec<&str> = decoded.split('/').collect();
1032 let last = parts.len().saturating_sub(1);
1033 let mut trailing_slash = false;
1034 for (n, segment) in parts.iter().enumerate() {
1035 match *segment {
1036 "" => trailing_slash = n == last,
1037 "." => trailing_slash = n == last,
1038 ".." => {
1039 segments.pop();
1040 trailing_slash = n == last;
1041 }
1042 s => {
1043 segments.push(s);
1044 trailing_slash = false;
1045 }
1046 }
1047 }
1048 let mut out = String::with_capacity(decoded.len() + query.len());
1049 out.push('/');
1050 out.push_str(&segments.join("/"));
1051 if trailing_slash && !segments.is_empty() {
1052 out.push('/');
1053 }
1054 out.push_str(query);
1055 if out == target {
1056 std::borrow::Cow::Borrowed(target)
1057 } else {
1058 std::borrow::Cow::Owned(out)
1059 }
1060}
1061
1062fn host_matches(expected: &str, actual: &str) -> bool {
1065 let actual = if expected.contains(':') {
1066 actual
1067 } else if let Some(rest) = actual.strip_prefix('[') {
1068 rest.split(']').next().unwrap_or(rest)
1070 } else {
1071 actual.rsplit_once(':').map_or(actual, |(host, port)| if port.bytes().all(|b| b.is_ascii_digit()) { host } else { actual })
1072 };
1073 let expected = expected.trim_start_matches('[').trim_end_matches(']');
1074 actual.eq_ignore_ascii_case(expected)
1075}
1076
1077impl Application for ConfigDrivenApp {
1078 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1079 let request = normalized(request);
1080 let generation = self.current();
1081 match &generation.entry {
1082 Some(entry) => entry.execute(&request, conn),
1083 None => generation.dispatch.execute(&request, conn),
1084 }
1085 }
1086
1087 fn streams_body(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1090 let request = normalized(request);
1091 let generation = self.current();
1092 match &generation.entry {
1093 Some(entry) => entry.streams_body(&request, conn),
1094 None => generation.dispatch.streams_body(&request, conn),
1095 }
1096 }
1097
1098 fn runs_inline(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1102 let request = normalized(request);
1103 let generation = self.current();
1104 generation.entry.is_none()
1105 && !generation.dispatch.routes.iter().any(|r| r.matcher.matches(&request, conn))
1106 && App::is_inline_request(&request)
1107 }
1108
1109 #[cfg(feature = "http2")]
1113 fn execute_async<'a>(&'a self, request: &'a Request, conn: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
1114 let generation = self.current();
1115 if generation.entry.is_some() {
1116 return None;
1117 }
1118 let request = normalized(request);
1119 let route = generation.dispatch.routes.iter().find(|r| r.matcher.matches(&request, conn))?;
1120 let proxy = Arc::clone(route.async_proxy.as_ref()?);
1121 Some(Box::pin(async move { proxy.execute_async(&request, conn).await }))
1122 }
1123}
1124
1125#[cfg(feature = "http2")]
1131pub(crate) struct H2Route {
1132 live: Arc<RwLock<Vec<String>>>,
1133 tls: bool,
1134 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1135 connect_timeout: std::time::Duration,
1136 read_timeout: std::time::Duration,
1137 counter: std::sync::atomic::AtomicUsize,
1138}
1139
1140#[cfg(feature = "http2")]
1141impl H2Route {
1142 pub(crate) fn new(
1143 live: Arc<RwLock<Vec<String>>>,
1144 tls: bool,
1145 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1146 connect_timeout_ms: u64,
1147 read_timeout_ms: u64,
1148 ) -> H2Route {
1149 H2Route {
1150 live,
1151 tls,
1152 trust,
1153 connect_timeout: std::time::Duration::from_millis(connect_timeout_ms),
1154 read_timeout: std::time::Duration::from_millis(read_timeout_ms),
1155 counter: std::sync::atomic::AtomicUsize::new(0),
1156 }
1157 }
1158}
1159
1160#[cfg(feature = "http2")]
1161impl Application for H2Route {
1162 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1163 use crate::middleware::Middleware;
1164 crate::proxy::GrpcWebLayer::new().handle(request, conn, &H2Forward(self))
1165 }
1166
1167 fn streams_body(&self, _request: &Request, _conn: &ConnectionInfo) -> bool {
1168 true
1169 }
1170}
1171
1172#[cfg(feature = "http2")]
1174struct H2Forward<'a>(&'a H2Route);
1175
1176#[cfg(feature = "http2")]
1177impl Application for H2Forward<'_> {
1178 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1179 let route = self.0;
1180 let backends = route.live.read().unwrap_or_else(|e| e.into_inner()).clone();
1181 if backends.is_empty() {
1182 return Ok(crate::proxy::bad_gateway());
1183 }
1184 let attempts = if matches!(request.body, crate::request::Body::Stream(_)) { 1 } else { backends.len() };
1186 let start = route.counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1187 for attempt in 0..attempts {
1188 let url = &backends[(start + attempt) % backends.len()];
1189 let result = crate::proxy::forward_h2(
1190 request, &conn.client.ip, url, route.tls, route.trust.as_deref(), route.connect_timeout, route.read_timeout,
1191 );
1192 match result {
1193 Ok(response) => return Ok(response),
1194 Err(e) => eprintln!("grpc route: {e}"),
1195 }
1196 }
1197 Ok(crate::proxy::bad_gateway())
1198 }
1199}
1200
1201#[derive(Clone, Copy)]
1206pub(crate) struct NullApp;
1207
1208impl Application for NullApp {
1209 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1210 let mut r = Response::new();
1211 r.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1212 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1213 Ok(r)
1214 }
1215}
1216
1217use std::collections::HashMap;
1220use std::collections::hash_map::DefaultHasher;
1221use std::hash::{Hash, Hasher};
1222use std::sync::atomic::AtomicUsize;
1223use std::time::{Duration, SystemTime, UNIX_EPOCH};
1224
1225#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1229pub(crate) enum LoadBalanceStrategy {
1230 RoundRobin,
1231 Random,
1232 IpHash,
1233 LeastConnections,
1234}
1235
1236impl LoadBalanceStrategy {
1237 fn parse(s: &str) -> Self {
1238 match s {
1239 "random" => LoadBalanceStrategy::Random,
1240 "ip_hash" => LoadBalanceStrategy::IpHash,
1241 "least_connections" => LoadBalanceStrategy::LeastConnections,
1242 _ => LoadBalanceStrategy::RoundRobin,
1243 }
1244 }
1245}
1246
1247#[derive(Clone)]
1253pub(crate) struct DynamicProxy {
1254 live: Arc<RwLock<Vec<String>>>,
1255 counter: Arc<AtomicUsize>,
1256 connect_timeout: Duration,
1257 read_timeout: Duration,
1258 strip_prefix: Option<Arc<String>>,
1259 add_prefix: Option<Arc<String>>,
1260 tls: bool,
1261 strategy: LoadBalanceStrategy,
1262 connections: Arc<RwLock<HashMap<String, Arc<AtomicUsize>>>>,
1263 trust: Option<Arc<crate::proxy::UpstreamTls>>,
1265}
1266
1267impl DynamicProxy {
1268 pub(crate) fn with_trust(mut self, trust: Option<Arc<crate::proxy::UpstreamTls>>) -> Self {
1270 self.trust = trust;
1271 self
1272 }
1273
1274 pub(crate) fn new(
1275 live: Arc<RwLock<Vec<String>>>,
1276 connect_timeout_ms: u64,
1277 read_timeout_ms: u64,
1278 strip_prefix: Option<String>,
1279 add_prefix: Option<String>,
1280 tls: bool,
1281 strategy: String,
1282 ) -> Self {
1283 DynamicProxy {
1284 live,
1285 counter: Arc::new(AtomicUsize::new(0)),
1286 connect_timeout: Duration::from_millis(connect_timeout_ms),
1287 read_timeout: Duration::from_millis(read_timeout_ms),
1288 strip_prefix: strip_prefix.map(Arc::new),
1289 add_prefix: add_prefix.map(Arc::new),
1290 tls,
1291 strategy: LoadBalanceStrategy::parse(&strategy),
1292 connections: Arc::new(RwLock::new(HashMap::new())),
1293 trust: None,
1294 }
1295 }
1296
1297 fn next_backend(&self, client_ip: &str) -> Option<String> {
1298 let live = self.live.read().unwrap();
1299 if live.is_empty() {
1300 return None;
1301 }
1302
1303 let idx = match self.strategy {
1304 LoadBalanceStrategy::RoundRobin => {
1305 self.counter.fetch_add(1, Ordering::Relaxed) % live.len()
1306 }
1307 LoadBalanceStrategy::Random => {
1308 let nanos = SystemTime::now()
1309 .duration_since(UNIX_EPOCH)
1310 .map(|d| d.subsec_nanos())
1311 .unwrap_or(0) as usize;
1312 let salt = self.counter.fetch_add(1, Ordering::Relaxed);
1313 nanos.wrapping_add(salt) % live.len()
1314 }
1315 LoadBalanceStrategy::IpHash => {
1316 let mut hasher = DefaultHasher::new();
1317 client_ip.hash(&mut hasher);
1318 (hasher.finish() as usize) % live.len()
1319 }
1320 LoadBalanceStrategy::LeastConnections => {
1321 let connections = self.connections.read().unwrap();
1322 live.iter()
1323 .enumerate()
1324 .min_by_key(|(_, backend)| {
1325 connections
1326 .get(*backend)
1327 .map(|c| c.load(Ordering::Relaxed))
1328 .unwrap_or(0)
1329 })
1330 .map(|(i, _)| i)
1331 .unwrap_or(0)
1332 }
1333 };
1334
1335 Some(live[idx].clone())
1336 }
1337
1338 fn connection_counter(&self, backend: &str) -> Arc<AtomicUsize> {
1341 if let Some(counter) = self.connections.read().unwrap().get(backend) {
1342 return Arc::clone(counter);
1343 }
1344 let mut connections = self.connections.write().unwrap();
1345 Arc::clone(
1346 connections
1347 .entry(backend.to_string())
1348 .or_insert_with(|| Arc::new(AtomicUsize::new(0))),
1349 )
1350 }
1351}
1352
1353struct ConnectionGuard {
1356 counter: Arc<AtomicUsize>,
1357}
1358
1359impl Drop for ConnectionGuard {
1360 fn drop(&mut self) {
1361 self.counter.fetch_sub(1, Ordering::Relaxed);
1362 }
1363}
1364
1365struct Prepared<'a> {
1368 host: String,
1369 port: u16,
1370 request: std::borrow::Cow<'a, Request>,
1371 _guard: Option<ConnectionGuard>,
1372}
1373
1374impl DynamicProxy {
1375 fn prepare<'a>(&self, request: &'a Request, conn: &ConnectionInfo) -> Result<Prepared<'a>, Response> {
1378 let backend = self.next_backend(&conn.client.ip).ok_or_else(bad_gateway)?;
1379
1380 let guard = if self.strategy == LoadBalanceStrategy::LeastConnections {
1381 let counter = self.connection_counter(&backend);
1382 counter.fetch_add(1, Ordering::Relaxed);
1383 Some(ConnectionGuard { counter })
1384 } else {
1385 None
1386 };
1387
1388 let (host, port, _) = crate::proxy_config::health::parse_backend_url(&backend).ok_or_else(bad_gateway)?;
1389
1390 let request = if self.strip_prefix.is_some() || self.add_prefix.is_some() {
1392 let mut req_clone = request.clone();
1393 if let Some(ref sp) = self.strip_prefix {
1394 if let Some(stripped) = req_clone.request_uri.strip_prefix(sp.as_str()) {
1395 req_clone.request_uri = if stripped.is_empty() || !stripped.starts_with('/') {
1396 format!("/{}", stripped)
1397 } else {
1398 stripped.to_string()
1399 };
1400 }
1401 }
1402 if let Some(ref ap) = self.add_prefix {
1403 req_clone.request_uri = format!("{}{}", ap, req_clone.request_uri);
1404 }
1405 std::borrow::Cow::Owned(req_clone)
1406 } else {
1407 std::borrow::Cow::Borrowed(request)
1408 };
1409
1410 Ok(Prepared { host, port, request, _guard: guard })
1411 }
1412
1413 #[cfg(feature = "http2")]
1416 pub(crate) async fn execute_async(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1417 let prepared = match self.prepare(request, conn) {
1418 Ok(p) => p,
1419 Err(response) => return Ok(response),
1420 };
1421 let tls = self.tls.then(|| match &self.trust {
1422 Some(trust) => trust.async_tls(),
1423 None => crate::proxy::async_upstream::AsyncTls {
1424 config: crate::proxy::tls_client_config(),
1425 pool: crate::proxy::async_upstream::default_tls_pool(),
1426 },
1427 });
1428 let result = crate::proxy::async_upstream::forward(
1429 &prepared.request,
1430 &conn.client.ip,
1431 &prepared.host,
1432 prepared.port,
1433 tls,
1434 self.connect_timeout,
1435 self.read_timeout,
1436 )
1437 .await;
1438 Ok(result.unwrap_or_else(|_| bad_gateway()))
1439 }
1440
1441 pub(crate) fn supports_async(&self) -> bool {
1443 cfg!(feature = "http2")
1444 }
1445}
1446
1447impl Application for DynamicProxy {
1448 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1449 let prepared = match self.prepare(request, conn) {
1450 Ok(p) => p,
1451 Err(response) => return Ok(response),
1452 };
1453
1454 let result = crate::proxy::forward_upstream_trusting(
1457 crate::proxy::shared_pool(),
1458 &prepared.request,
1459 &conn.client.ip,
1460 &prepared.host,
1461 prepared.port,
1462 self.tls,
1463 self.trust.as_deref(),
1464 self.connect_timeout,
1465 self.read_timeout,
1466 );
1467
1468 match result {
1469 Ok(r) => Ok(r),
1470 Err(_) => Ok(bad_gateway()),
1471 }
1472 }
1473}
1474
1475fn bad_gateway() -> Response {
1476 use crate::mime_type::MimeType;
1477 use crate::range::Range;
1478 let cr = Range::get_content_range(
1479 b"502 Bad Gateway".to_vec(),
1480 MimeType::TEXT_PLAIN.to_string(),
1481 );
1482 let mut r = Response::new();
1483 r.status_code = *STATUS_CODE_REASON_PHRASE.n502_bad_gateway.status_code;
1484 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n502_bad_gateway.reason_phrase.to_string();
1485 r.content_range_list = vec![cr];
1486 r
1487}
1488
1489#[derive(Clone)]
1495pub(crate) struct RedirectAdapter {
1496 location_template: Arc<String>,
1497 status: i16,
1498 reason: Arc<String>,
1499}
1500
1501impl RedirectAdapter {
1502 pub(crate) fn new(location: String, status: u16) -> Self {
1503 let (code, reason) = redirect_status(status);
1504 RedirectAdapter {
1505 location_template: Arc::new(location),
1506 status: code,
1507 reason: Arc::new(reason),
1508 }
1509 }
1510}
1511
1512fn redirect_status(code: u16) -> (i16, String) {
1513 let phrase = match code {
1514 301 => STATUS_CODE_REASON_PHRASE.n301_moved_permanently.reason_phrase,
1515 302 => STATUS_CODE_REASON_PHRASE.n302_found.reason_phrase,
1516 307 => STATUS_CODE_REASON_PHRASE.n307_temporary_redirect.reason_phrase,
1517 308 => STATUS_CODE_REASON_PHRASE.n308_permanent_redirect.reason_phrase,
1518 _ => "Redirect",
1519 };
1520 (code as i16, phrase.to_string())
1521}
1522
1523impl Application for RedirectAdapter {
1524 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1525 let location = self
1526 .location_template
1527 .replace("$path", &request.request_uri);
1528 use crate::header::Header;
1529 let mut r = Response::new();
1530 r.status_code = self.status;
1531 r.reason_phrase = self.reason.as_ref().clone();
1532 r.headers.push(Header { name: "Location".into(), value: location.into() });
1533 Ok(r)
1534 }
1535}
1536
1537#[derive(Clone)]
1541pub(crate) struct RespondAdapter {
1542 status: i16,
1543 reason: Arc<String>,
1544 body: Arc<Vec<u8>>,
1545 content_type: Arc<String>,
1546}
1547
1548impl RespondAdapter {
1549 pub(crate) fn new(status: u16, body: String, content_type: String) -> Self {
1550 use crate::response::STATUS_CODE_REASON_PHRASE;
1551 let reason = match status {
1552 200 => STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
1553 201 => STATUS_CODE_REASON_PHRASE.n201_created.reason_phrase.to_string(),
1554 204 => STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string(),
1555 400 => STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string(),
1556 401 => STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string(),
1557 403 => STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string(),
1558 404 => STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string(),
1559 500 => STATUS_CODE_REASON_PHRASE.n500_internal_server_error.reason_phrase.to_string(),
1560 _ => "OK".to_string(),
1561 };
1562 RespondAdapter {
1563 status: status as i16,
1564 reason: Arc::new(reason),
1565 body: Arc::new(body.into_bytes()),
1566 content_type: Arc::new(content_type),
1567 }
1568 }
1569}
1570
1571impl Application for RespondAdapter {
1572 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1573 use crate::range::Range;
1574 let mut r = Response::new();
1575 r.status_code = self.status;
1576 r.reason_phrase = self.reason.as_ref().clone();
1577 if !self.body.is_empty() {
1578 r.content_range_list = vec![Range::get_content_range(
1579 self.body.as_ref().clone(),
1580 self.content_type.as_ref().clone(),
1581 )];
1582 }
1583 Ok(r)
1584 }
1585}
1586
1587#[derive(Clone)]
1596pub(crate) struct StaticAdapter {
1597 root: Arc<std::path::PathBuf>,
1598 index: Arc<Vec<String>>,
1599}
1600
1601impl StaticAdapter {
1602 pub(crate) fn new(root: String, index: Vec<String>) -> Self {
1603 let index = if index.is_empty() { vec!["index.html".to_string()] } else { index };
1604 StaticAdapter {
1605 root: Arc::new(std::path::PathBuf::from(root)),
1606 index: Arc::new(index),
1607 }
1608 }
1609
1610 fn resolve(&self, request_uri: &str) -> Option<std::path::PathBuf> {
1614 let raw_path = request_uri.split('?').next().unwrap_or(request_uri);
1615 let decoded = crate::url::URL::percent_decode(raw_path);
1616
1617 if decoded.split('/').any(|segment| segment == "..") {
1618 return None;
1619 }
1620
1621 let relative = decoded.trim_start_matches('/');
1622 Some(self.root.join(relative))
1623 }
1624}
1625
1626impl Application for StaticAdapter {
1627 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1628 let mut response = Response::new();
1629
1630 let not_found = |mut response: Response| {
1631 response.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1632 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1633 response
1634 };
1635
1636 let candidate = match self.resolve(&request.request_uri) {
1637 Some(p) => p,
1638 None => {
1639 response.status_code = *STATUS_CODE_REASON_PHRASE.n403_forbidden.status_code;
1640 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string();
1641 return Ok(response);
1642 }
1643 };
1644
1645 let mut file_path = candidate;
1646 if file_path.is_dir() {
1647 let indexed = self
1648 .index
1649 .iter()
1650 .map(|name| file_path.join(name))
1651 .find(|p| p.is_file());
1652
1653 file_path = match indexed {
1654 Some(p) => p,
1655 None => return Ok(not_found(response)),
1656 };
1657 }
1658
1659 if !file_path.is_file() {
1660 return Ok(not_found(response));
1661 }
1662
1663 if let (Ok(root_canon), Ok(file_canon)) =
1666 (self.root.canonicalize(), file_path.canonicalize())
1667 {
1668 if !file_canon.starts_with(&root_canon) {
1669 return Ok(not_found(response));
1670 }
1671 }
1672
1673 let path_str = match file_path.to_str() {
1674 Some(s) => s,
1675 None => return Ok(not_found(response)),
1676 };
1677
1678 match crate::range::Range::get_content_range_of_a_file(path_str) {
1679 Ok(content_range) => {
1680 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
1681 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
1682 response.content_range_list = vec![content_range];
1683 Ok(response)
1684 }
1685 Err(_) => Ok(not_found(response)),
1686 }
1687 }
1688}
1689
1690pub(crate) struct PerRouteRateLimit(pub(crate) Arc<crate::rate_limit::RateLimiter>);
1694
1695impl crate::middleware::Middleware for PerRouteRateLimit {
1696 fn handle(
1697 &self,
1698 request: &Request,
1699 conn: &ConnectionInfo,
1700 next: &dyn Application,
1701 ) -> Result<Response, String> {
1702 use crate::error::{AppError, IntoResponse};
1703 if self.0.check(&conn.client.ip) {
1704 next.execute(request, conn)
1705 } else {
1706 let mut response = AppError::TooManyRequests.into_response();
1707 let retry_after = self.0.retry_after(&conn.client.ip);
1708 response.headers.push(Header {
1709 name: Header::_RETRY_AFTER.into(),
1710 value: retry_after.to_string().into(),
1711 });
1712 Ok(response)
1713 }
1714 }
1715}
1716
1717pub(crate) struct PerRouteMaxBodySize(pub(crate) u64);
1732
1733impl crate::middleware::Middleware for PerRouteMaxBodySize {
1734 fn handle(
1735 &self,
1736 request: &Request,
1737 conn: &ConnectionInfo,
1738 next: &dyn Application,
1739 ) -> Result<Response, String> {
1740 use crate::error::{AppError, IntoResponse};
1741 if request.body.len() as u64 > self.0 {
1742 Ok(AppError::PayloadTooLarge(format!(
1743 "request body of {} bytes exceeds this route's {}-byte limit",
1744 request.body.len(),
1745 self.0,
1746 ))
1747 .into_response())
1748 } else {
1749 next.execute(request, conn)
1750 }
1751 }
1752}
1753
1754pub(crate) struct BearerAuthMiddleware {
1758 pub(crate) token: Arc<String>,
1759}
1760
1761impl crate::middleware::Middleware for BearerAuthMiddleware {
1762 fn handle(
1763 &self,
1764 request: &Request,
1765 conn: &ConnectionInfo,
1766 next: &dyn Application,
1767 ) -> Result<Response, String> {
1768 use crate::error::{AppError, IntoResponse};
1769 let expected = format!("Bearer {}", self.token);
1770 let authorized = request
1771 .headers
1772 .iter()
1773 .any(|h| h.name.eq_ignore_ascii_case("authorization") && constant_time_eq(h.value.as_bytes(), expected.as_bytes()));
1774 if authorized {
1775 next.execute(request, conn)
1776 } else {
1777 Ok(AppError::Unauthorized.into_response())
1778 }
1779 }
1780}
1781
1782pub(crate) fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
1785 if a.len() != b.len() {
1786 return false;
1787 }
1788 a.iter().zip(b).fold(0u8, |diff, (x, y)| diff | (x ^ y)) == 0
1789}
1790
1791pub(crate) struct AuthMisconfigured;
1794
1795impl crate::middleware::Middleware for AuthMisconfigured {
1796 fn handle(
1797 &self,
1798 _request: &Request,
1799 _conn: &ConnectionInfo,
1800 _next: &dyn Application,
1801 ) -> Result<Response, String> {
1802 Ok(Response::text(
1803 STATUS_CODE_REASON_PHRASE.n503_service_unavailable,
1804 "503 Service Unavailable: authentication for this route is misconfigured",
1805 ))
1806 }
1807}
1808
1809pub(crate) fn arc_app<A: Application + Send + Sync + 'static>(
1813 a: A,
1814) -> Arc<dyn Application + Send + Sync> {
1815 Arc::new(a)
1816}
1817
1818pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
1823 builder::build_from_file()
1824}