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}
71
72#[derive(Debug, Clone)]
73pub struct HealthCheckConfig {
74 pub path: String,
75 pub interval_secs: u64,
76 pub timeout_ms: u64,
77 pub healthy_threshold: u32,
78 pub unhealthy_threshold: u32,
79}
80
81#[derive(Debug, Clone)]
82pub struct RouteConfig {
83 pub name: String,
84 pub match_: MatchConfig,
85 pub action: ActionConfig,
86 pub middleware: MiddlewareConfig,
87}
88
89#[derive(Debug, Clone, Default)]
90pub struct MatchConfig {
91 pub host: Option<String>,
92 pub path: Option<String>,
93 pub method: Option<String>,
94 pub content_type: Option<String>,
95}
96
97#[derive(Debug, Clone)]
98pub enum ActionConfig {
99 Proxy {
100 upstream: String,
101 connect_timeout_ms: u64,
102 read_timeout_ms: u64,
103 strip_path_prefix: Option<String>,
104 add_path_prefix: Option<String>,
105 },
106 Grpc {
107 upstream: String,
108 connect_timeout_ms: u64,
109 read_timeout_ms: u64,
110 },
111 Static {
112 root: String,
113 index: Vec<String>,
114 },
115 Redirect {
116 location: String,
117 status: u16,
118 },
119 Respond {
120 status: u16,
121 body: String,
122 content_type: String,
123 },
124 Mcp,
125 Unknown(String),
126}
127
128#[derive(Debug, Clone, Default)]
129pub struct MiddlewareConfig {
130 pub rate_limit: Option<RateLimitConfig>,
131 pub cache: Option<CacheConfig>,
132 pub auth: Option<AuthConfig>,
133 pub rewrite_request: Vec<RewriteRuleConfig>,
134 pub rewrite_response: Vec<RewriteRuleConfig>,
135 pub ip_allow: Vec<String>,
136 pub ip_deny: Vec<String>,
137 pub timeout_ms: Option<u64>,
142 pub max_body_size: Option<u64>,
158}
159
160#[derive(Debug, Clone)]
161pub struct RateLimitConfig {
162 pub max_requests: u32,
163 pub window_secs: u64,
164}
165
166#[derive(Debug, Clone)]
167pub struct CacheConfig {
168 pub ttl_secs: u64,
169 pub vary_by: Vec<String>,
170}
171
172#[derive(Debug, Clone)]
173pub enum AuthConfig {
174 Basic { htpasswd_file: String },
179 Jwt { secret_env: String },
182 Bearer { token_env: String },
183}
184
185#[derive(Debug, Clone, Default)]
186pub struct RewriteRuleConfig {
187 pub type_: String,
188 pub name: Option<String>,
189 pub value: Option<String>,
190 pub prefix: Option<String>,
191 pub from: Option<String>,
192 pub to: Option<String>,
193 pub code: Option<u16>,
194 pub reason: Option<String>,
195}
196
197#[derive(Debug, Clone)]
198pub struct TcpProxyConfig {
199 pub name: String,
200 pub listen: String,
201 pub backends: Vec<String>,
202 pub connect_timeout_ms: u64,
203}
204
205#[derive(Debug, Clone)]
206pub struct UdpProxyConfig {
207 pub name: String,
208 pub listen: String,
209 pub backends: Vec<String>,
210 pub reply_timeout_ms: u64,
211 pub buffer_size: usize,
212}
213
214#[derive(Debug, Clone)]
215pub struct WsProxyConfig {
216 pub name: String,
217 pub listen: String,
218 pub backends: Vec<String>,
219 pub connect_timeout_ms: u64,
220 pub read_timeout_ms: u64,
221 pub health_check: Option<HealthCheckConfig>,
228}
229
230fn merge_action_keys(map: &mut parser::SectionMap, action_section: &str, action_type: &str) {
239 if action_type.is_empty() {
240 return;
241 }
242 let short_form: Vec<(String, String)> = match map.get(action_section) {
243 Some(entries) => entries.iter().filter(|(k, _)| k != "type").cloned().collect(),
244 None => return,
245 };
246 let nested = map.entry(format!("{action_section}.{action_type}")).or_default();
247 for (key, value) in short_form {
248 if !nested.iter().any(|(k, _)| *k == key) {
249 nested.push((key, value));
250 }
251 }
252}
253
254impl ProxyConfig {
255 pub fn is_proxy_mode() -> bool {
259 let path = config_file_path();
260 match std::fs::read_to_string(&path) {
261 Ok(contents) => {
262 contents.contains("[[route]]") || contents.contains("[[upstream]]")
263 }
264 Err(_) => false,
265 }
266 }
267
268 pub fn load() -> Self {
270 let path = config_file_path();
271 let contents = std::fs::read_to_string(&path).unwrap_or_default();
272 Self::from_str(&contents)
273 }
274
275 pub fn from_str(toml: &str) -> Self {
277 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
278
279 let mut map = parser::parse(toml);
280
281 let mut upstreams = Vec::new();
283 let mut i = 0;
284 loop {
285 let sec = format!("upstream[{}]", i);
286 if !section_exists(&map, &sec) {
287 break;
288 }
289 let name = get_str(&map, &sec, "name");
290 let backends = get_array(&map, &sec, "backends");
291 let strategy = {
292 let s = get_str(&map, &sec, "strategy");
293 if s.is_empty() { "round_robin".to_string() } else { s }
294 };
295 let hc_sec = format!("{}.health_check", sec);
296 let health_check = if section_exists(&map, &hc_sec) {
297 Some(HealthCheckConfig {
298 path: {
299 let p = get_str(&map, &hc_sec, "path");
300 if p.is_empty() { "/health".to_string() } else { p }
301 },
302 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
303 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
304 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
305 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
306 })
307 } else {
308 None
309 };
310 let tls = backends.iter().any(|b| b.starts_with("https://"));
311 upstreams.push(UpstreamConfig { name, backends, strategy, health_check, tls });
312 i += 1;
313 }
314
315 let mut routes = Vec::new();
317 let mut i = 0;
318 loop {
319 let sec = format!("route[{}]", i);
320 if !section_exists(&map, &sec) {
321 break;
322 }
323 let name = get_str(&map, &sec, "name");
324
325 let m_sec = format!("{}.match", sec);
327 let match_ = MatchConfig {
328 host: {
329 let h = get_str(&map, &m_sec, "host");
330 if h.is_empty() { None } else { Some(h) }
331 },
332 path: {
333 let p = get_str(&map, &m_sec, "path");
334 if p.is_empty() { None } else { Some(p) }
335 },
336 method: {
337 let m = get_str(&map, &m_sec, "method");
338 if m.is_empty() { None } else { Some(m.to_uppercase()) }
339 },
340 content_type: {
341 let c = get_str(&map, &m_sec, "content_type");
342 if c.is_empty() { None } else { Some(c) }
343 },
344 };
345
346 let a_sec = format!("{}.action", sec);
348 let action_type = get_str(&map, &a_sec, "type");
349 merge_action_keys(&mut map, &a_sec, &action_type);
350 let action = match action_type.as_str() {
351 "proxy" => {
352 let p_sec = format!("{}.action.proxy", sec);
353 ActionConfig::Proxy {
354 upstream: get_str(&map, &p_sec, "upstream"),
355 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
356 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
357 strip_path_prefix: {
358 let v = get_str(&map, &p_sec, "strip_path_prefix");
359 if v.is_empty() { None } else { Some(v) }
360 },
361 add_path_prefix: {
362 let v = get_str(&map, &p_sec, "add_path_prefix");
363 if v.is_empty() { None } else { Some(v) }
364 },
365 }
366 }
367 "grpc" => {
368 let p_sec = format!("{}.action.grpc", sec);
369 ActionConfig::Grpc {
370 upstream: get_str(&map, &p_sec, "upstream"),
371 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
372 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
373 }
374 }
375 "static" => {
376 let s_sec = format!("{}.action.static", sec);
377 ActionConfig::Static {
378 root: get_str(&map, &s_sec, "root"),
379 index: get_array(&map, &s_sec, "index"),
380 }
381 }
382 "redirect" => {
383 let r_sec = format!("{}.action.redirect", sec);
384 ActionConfig::Redirect {
385 location: get_str(&map, &r_sec, "location"),
386 status: get_u64(&map, &r_sec, "status", 301) as u16,
387 }
388 }
389 "respond" => {
390 let r_sec = format!("{}.action.respond", sec);
391 ActionConfig::Respond {
392 status: get_u64(&map, &r_sec, "status", 200) as u16,
393 body: get_str(&map, &r_sec, "body"),
394 content_type: {
395 let ct = get_str(&map, &r_sec, "content_type");
396 if ct.is_empty() { "text/plain".to_string() } else { ct }
397 },
398 }
399 }
400 "mcp" => ActionConfig::Mcp,
401 other => ActionConfig::Unknown(other.to_string()),
402 };
403
404 let mw_sec = format!("{}.middleware", sec);
406 let middleware = parse_middleware_config(&map, &mw_sec, i);
407
408 routes.push(RouteConfig { name, match_, action, middleware });
409 i += 1;
410 }
411
412 let mut tcp_proxies = Vec::new();
414 let mut i = 0;
415 loop {
416 let sec = format!("tcp_proxy[{}]", i);
417 if !section_exists(&map, &sec) {
418 break;
419 }
420 tcp_proxies.push(TcpProxyConfig {
421 name: get_str(&map, &sec, "name"),
422 listen: get_str(&map, &sec, "listen"),
423 backends: get_array(&map, &sec, "backends"),
424 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
425 });
426 i += 1;
427 }
428
429 let mut udp_proxies = Vec::new();
431 let mut i = 0;
432 loop {
433 let sec = format!("udp_proxy[{}]", i);
434 if !section_exists(&map, &sec) {
435 break;
436 }
437 udp_proxies.push(UdpProxyConfig {
438 name: get_str(&map, &sec, "name"),
439 listen: get_str(&map, &sec, "listen"),
440 backends: get_array(&map, &sec, "backends"),
441 reply_timeout_ms: get_u64(&map, &sec, "reply_timeout_ms", 5000),
442 buffer_size: get_u64(&map, &sec, "buffer_size", 65536) as usize,
443 });
444 i += 1;
445 }
446
447 let mut ws_proxies = Vec::new();
449 let mut i = 0;
450 loop {
451 let sec = format!("ws_proxy[{}]", i);
452 if !section_exists(&map, &sec) {
453 break;
454 }
455 let hc_sec = format!("{}.health_check", sec);
456 let health_check = if section_exists(&map, &hc_sec) {
457 Some(HealthCheckConfig {
458 path: {
459 let p = get_str(&map, &hc_sec, "path");
460 if p.is_empty() { "/health".to_string() } else { p }
461 },
462 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
463 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
464 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
465 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
466 })
467 } else {
468 None
469 };
470 ws_proxies.push(WsProxyConfig {
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 read_timeout_ms: get_u64(&map, &sec, "read_timeout_ms", 30000),
476 health_check,
477 });
478 i += 1;
479 }
480
481 let global_middleware = parse_middleware_config(&map, "middleware", usize::MAX);
483
484 ProxyConfig {
485 upstreams,
486 routes,
487 tcp_proxies,
488 udp_proxies,
489 ws_proxies,
490 global_middleware,
491 }
492 }
493}
494
495fn parse_middleware_config(
498 map: &parser::SectionMap,
499 mw_sec: &str,
500 route_idx: usize,
501) -> MiddlewareConfig {
502 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
503
504 let rl_sec = format!("{}.rate_limit", mw_sec);
505 let rate_limit = if section_exists(map, &rl_sec) {
506 Some(RateLimitConfig {
507 max_requests: get_u32(map, &rl_sec, "max_requests", 1000),
508 window_secs: get_u64(map, &rl_sec, "window_secs", 60),
509 })
510 } else {
511 None
512 };
513
514 let c_sec = format!("{}.cache", mw_sec);
515 let cache = if section_exists(map, &c_sec) {
516 Some(CacheConfig {
517 ttl_secs: get_u64(map, &c_sec, "ttl_secs", 60),
518 vary_by: get_array(map, &c_sec, "vary_by"),
519 })
520 } else {
521 None
522 };
523
524 let a_sec = format!("{}.auth", mw_sec);
525 let auth = if section_exists(map, &a_sec) {
526 let auth_type = get_str(map, &a_sec, "type");
527 match auth_type.as_str() {
528 "bearer" => Some(AuthConfig::Bearer {
529 token_env: get_str(map, &a_sec, "token_env"),
530 }),
531 "jwt" => Some(AuthConfig::Jwt {
532 secret_env: get_str(map, &a_sec, "secret_env"),
533 }),
534 "basic" => Some(AuthConfig::Basic {
535 htpasswd_file: get_str(map, &a_sec, "htpasswd_file"),
536 }),
537 _ => None,
538 }
539 } else {
540 None
541 };
542
543 let rewrite_request = collect_rewrite_rules(map, mw_sec, "request");
549 let rewrite_response = collect_rewrite_rules(map, mw_sec, "response");
550
551 let ip_sec = format!("{}.ip_filter", mw_sec);
552 let ip_allow = if section_exists(map, &ip_sec) {
553 get_array(map, &ip_sec, "allow")
554 } else {
555 vec![]
556 };
557 let ip_deny = if section_exists(map, &ip_sec) {
558 get_array(map, &ip_sec, "deny")
559 } else {
560 vec![]
561 };
562
563 let _ = route_idx; let timeout_ms = match get_u64(map, mw_sec, "timeout_ms", 0) {
569 0 => None,
570 ms => Some(ms),
571 };
572
573 let max_body_size = match get_u64(map, mw_sec, "max_body_size", 0) {
577 0 => None,
578 n => Some(n),
579 };
580
581 MiddlewareConfig {
582 rate_limit, cache, auth, rewrite_request, rewrite_response, ip_allow, ip_deny,
583 timeout_ms, max_body_size,
584 }
585}
586
587fn collect_rewrite_rules(
589 map: &parser::SectionMap,
590 mw_sec: &str,
591 direction: &str,
592) -> Vec<RewriteRuleConfig> {
593 use parser::{get_str, get_u64};
594
595 let mut rules = Vec::new();
596 let mut j = 0;
597 loop {
598 let rsec = format!("{}.rewrite.{}[{}]", mw_sec, direction, j);
599 if !parser::section_exists(map, &rsec) {
600 break;
601 }
602 let code_val = get_u64(map, &rsec, "code", 0);
603 rules.push(RewriteRuleConfig {
604 type_: get_str(map, &rsec, "type"),
605 name: {
606 let v = get_str(map, &rsec, "name");
607 if v.is_empty() { None } else { Some(v) }
608 },
609 value: {
610 let v = get_str(map, &rsec, "value");
611 if v.is_empty() { None } else { Some(v) }
612 },
613 prefix: {
614 let v = get_str(map, &rsec, "prefix");
615 if v.is_empty() { None } else { Some(v) }
616 },
617 from: {
618 let v = get_str(map, &rsec, "from");
619 if v.is_empty() { None } else { Some(v) }
620 },
621 to: {
622 let v = get_str(map, &rsec, "to");
623 if v.is_empty() { None } else { Some(v) }
624 },
625 code: if code_val == 0 { None } else { Some(code_val as u16) },
626 reason: {
627 let v = get_str(map, &rsec, "reason");
628 if v.is_empty() { None } else { Some(v) }
629 },
630 });
631 j += 1;
632 }
633 rules
634}
635
636pub(crate) fn config_file_path() -> String {
637 std::env::var("RWS_CONFIG_FILE").unwrap_or_else(|_| "rws.config.toml".to_string())
638}
639
640pub(crate) struct CompiledRoute {
644 pub(crate) matcher: RouteMatcher,
645 pub(crate) handler: Arc<dyn Application + Send + Sync>,
647 #[cfg_attr(not(feature = "http2"), allow(dead_code))]
651 pub(crate) async_proxy: Option<Arc<DynamicProxy>>,
652}
653
654#[derive(Clone, Default)]
656pub(crate) struct RouteMatcher {
657 pub(crate) host: Option<String>,
659 pub(crate) path_prefix: Option<String>,
661 pub(crate) path_exact: Option<String>,
663 pub(crate) method: Option<String>,
665 pub(crate) content_type_prefix: Option<String>,
667}
668
669impl RouteMatcher {
670 pub(crate) fn from_match_config(cfg: &MatchConfig) -> Self {
671 let (path_prefix, path_exact) = match &cfg.path {
672 Some(p) if p.ends_with('*') => {
673 let stripped = p.trim_end_matches('*').to_string();
675 (Some(stripped), None)
676 }
677 Some(p) => (None, Some(p.clone())),
678 None => (None, None),
679 };
680 let content_type_prefix = cfg.content_type.as_ref().map(|ct| {
681 if ct.ends_with('*') {
682 ct.trim_end_matches('*').to_string()
683 } else {
684 ct.clone()
685 }
686 });
687 RouteMatcher {
688 host: cfg.host.clone(),
689 path_prefix,
690 path_exact,
691 method: cfg.method.clone(),
692 content_type_prefix,
693 }
694 }
695
696 pub(crate) fn matches(&self, request: &Request, conn: &ConnectionInfo) -> bool {
698 if let Some(ref expected_host) = self.host {
700 let actual_host = conn
701 .sni_hostname
702 .as_deref()
703 .or_else(|| {
704 request
705 .headers
706 .iter()
707 .find(|h| h.name.eq_ignore_ascii_case("host"))
708 .map(|h| h.value.as_str())
709 })
710 .unwrap_or("");
711 if !host_matches(expected_host, actual_host) {
712 return false;
713 }
714 }
715
716 if let Some(ref m) = self.method {
718 if request.method.to_uppercase() != m.as_str() {
719 return false;
720 }
721 }
722
723 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
725 if let Some(ref prefix) = self.path_prefix {
726 if !path.starts_with(prefix.as_str()) {
727 return false;
728 }
729 } else if let Some(ref exact) = self.path_exact {
730 if path != exact.as_str() {
731 return false;
732 }
733 }
734
735 if let Some(ref ct_prefix) = self.content_type_prefix {
737 let actual_ct = request
738 .headers
739 .iter()
740 .find(|h| h.name.eq_ignore_ascii_case("content-type"))
741 .map(|h| h.value.as_str())
742 .unwrap_or("");
743 if !actual_ct.starts_with(ct_prefix.as_str()) {
744 return false;
745 }
746 }
747
748 true
749 }
750}
751
752#[derive(Clone)]
759pub struct ConfigDrivenApp {
760 current: Arc<RwLock<Arc<Generation>>>,
761 reload_lock: Arc<std::sync::Mutex<()>>,
763}
764
765struct Generation {
767 dispatch: RouteDispatch,
768 entry: Option<Arc<dyn Application + Send + Sync>>,
771 config: ProxyConfig,
772 source: Option<String>,
774 health_stop: Arc<AtomicBool>,
776}
777
778impl Generation {
779 fn new(routes: Arc<Vec<CompiledRoute>>, fallback: App, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
780 let dispatch = RouteDispatch { routes, fallback };
781 let entry = if config.global_middleware.is_empty() {
782 None
783 } else {
784 Some(builder::apply_middleware(arc_app(dispatch.clone()), &config.global_middleware))
785 };
786 Generation { dispatch, entry, config, source, health_stop }
787 }
788}
789
790#[derive(Clone)]
792struct RouteDispatch {
793 routes: Arc<Vec<CompiledRoute>>,
794 fallback: App,
799}
800
801impl Application for RouteDispatch {
802 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
803 for route in self.routes.iter() {
804 if route.matcher.matches(request, conn) {
805 return route.handler.execute(request, conn);
806 }
807 }
808 self.fallback.execute(request, conn)
809 }
810}
811
812impl MiddlewareConfig {
813 pub fn is_empty(&self) -> bool {
815 self.rate_limit.is_none() && self.cache.is_none() && self.auth.is_none()
816 && self.rewrite_request.is_empty() && self.rewrite_response.is_empty()
817 && self.ip_allow.is_empty() && self.ip_deny.is_empty()
818 && self.timeout_ms.is_none() && self.max_body_size.is_none()
819 }
820}
821
822#[derive(Debug, Clone, PartialEq)]
824pub enum ReloadOutcome {
825 Unchanged,
827 Reloaded { routes: usize, warnings: Vec<String> },
830}
831
832impl ConfigDrivenApp {
833 pub(crate) fn new(routes: Vec<CompiledRoute>, config: ProxyConfig, health_stop: Arc<AtomicBool>, source: Option<String>) -> Self {
834 use crate::core::New;
835 let generation = Generation::new(Arc::new(routes), App::new(), config, health_stop, source);
836 ConfigDrivenApp {
837 current: Arc::new(RwLock::new(Arc::new(generation))),
838 reload_lock: Arc::new(std::sync::Mutex::new(())),
839 }
840 }
841
842 fn current(&self) -> Arc<Generation> {
843 Arc::clone(&self.current.read().unwrap_or_else(|e| e.into_inner()))
844 }
845
846 pub fn with_config(self, config: ServerConfig) -> Self {
863 {
864 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
865 let old = Arc::clone(&slot);
866 *slot = Arc::new(Generation::new(
867 Arc::clone(&old.dispatch.routes),
868 App::with_config(config),
869 old.config.clone(),
870 Arc::clone(&old.health_stop),
871 old.source.clone(),
872 ));
873 }
874 self
875 }
876
877 pub fn reload_from_str(&self, toml: &str) -> Result<ReloadOutcome, crate::config_check::ConfigReport> {
887 let _serialized = self.reload_lock.lock().unwrap_or_else(|e| e.into_inner());
888 let old = self.current();
889 if old.source.as_deref() == Some(toml) {
890 return Ok(ReloadOutcome::Unchanged);
891 }
892 let config = ProxyConfig::from_str(toml);
893 let mut report = crate::config_check::ConfigReport::default();
894 validate::check_unknown_keys(toml, &mut report);
895 config.validate(&mut report);
896 if !report.is_ok() {
897 return Err(report);
898 }
899
900 let mut warnings = report.warnings;
901 let listeners = |c: &ProxyConfig| format!("{:?}{:?}{:?}", c.tcp_proxies, c.udp_proxies, c.ws_proxies);
902 if listeners(&config) != listeners(&old.config) {
903 warnings.push("[[tcp_proxy]] / [[udp_proxy]] / [[ws_proxy]] changes need a restart; the old listeners keep running".to_string());
904 }
905
906 let health_stop = Arc::new(AtomicBool::new(false));
907 let routes = builder::compile_routes(&config, &health_stop);
908 let route_count = routes.len();
909 let generation = Generation::new(Arc::new(routes), old.dispatch.fallback.clone(), config, health_stop, Some(toml.to_string()));
910 let replaced = {
911 let mut slot = self.current.write().unwrap_or_else(|e| e.into_inner());
912 std::mem::replace(&mut *slot, Arc::new(generation))
913 };
914 replaced.health_stop.store(true, Ordering::Relaxed);
915 Ok(ReloadOutcome::Reloaded { routes: route_count, warnings })
916 }
917
918 pub fn reload_from_file(&self) {
923 let path = config_file_path();
924 let toml = match std::fs::read_to_string(&path) {
925 Ok(toml) => toml,
926 Err(e) => {
927 eprintln!("[proxy_config] reload: cannot read {path}: {e} — keeping the running routes");
928 return;
929 }
930 };
931 match self.reload_from_str(&toml) {
932 Ok(ReloadOutcome::Unchanged) => {}
933 Ok(ReloadOutcome::Reloaded { routes, warnings }) => {
934 for w in &warnings {
935 eprintln!("[proxy_config] Warning: {w}");
936 }
937 println!("[proxy_config] reloaded {path}: {routes} route{}", if routes == 1 { "" } else { "s" });
938 }
939 Err(report) => {
940 for e in &report.errors {
941 eprintln!("[proxy_config] reload ERROR: {e}");
942 }
943 eprintln!("[proxy_config] {path} has errors — keeping the running routes");
944 }
945 }
946 }
947}
948
949fn normalized(request: &Request) -> std::borrow::Cow<'_, Request> {
952 match normalize_request_target(&request.request_uri) {
953 std::borrow::Cow::Borrowed(_) => std::borrow::Cow::Borrowed(request),
954 std::borrow::Cow::Owned(target) => {
955 let mut owned = request.clone();
956 owned.request_uri = target;
957 std::borrow::Cow::Owned(owned)
958 }
959 }
960}
961
962pub(crate) fn normalize_request_target(target: &str) -> std::borrow::Cow<'_, str> {
970 let (path, query) = match target.find('?') {
971 Some(i) => (&target[..i], &target[i..]),
972 None => (target, ""),
973 };
974 if !path.starts_with('/') || !(path.contains('%') || path.contains("//") || path.contains("/.")) {
975 return std::borrow::Cow::Borrowed(target);
976 }
977 let bytes = path.as_bytes();
978 let mut decoded = String::with_capacity(path.len());
979 let mut i = 0;
980 while i < bytes.len() {
981 let hex = |b: u8| (b as char).to_digit(16);
982 if bytes[i] == b'%' && i + 2 < bytes.len() {
983 if let (Some(h), Some(l)) = (hex(bytes[i + 1]), hex(bytes[i + 2])) {
984 let value = (h * 16 + l) as u8;
985 if value.is_ascii_alphanumeric() || matches!(value, b'-' | b'.' | b'_' | b'~') {
986 decoded.push(value as char);
987 } else {
988 decoded.push('%');
989 decoded.push(bytes[i + 1].to_ascii_uppercase() as char);
990 decoded.push(bytes[i + 2].to_ascii_uppercase() as char);
991 }
992 i += 3;
993 continue;
994 }
995 }
996 if bytes[i] == b'%' {
1000 decoded.push_str("%25");
1001 i += 1;
1002 continue;
1003 }
1004 let ch = path[i..].chars().next().unwrap_or('\u{FFFD}');
1006 decoded.push(ch);
1007 i += ch.len_utf8().max(1);
1008 }
1009 let mut segments: Vec<&str> = Vec::new();
1010 let parts: Vec<&str> = decoded.split('/').collect();
1011 let last = parts.len().saturating_sub(1);
1012 let mut trailing_slash = false;
1013 for (n, segment) in parts.iter().enumerate() {
1014 match *segment {
1015 "" => trailing_slash = n == last,
1016 "." => trailing_slash = n == last,
1017 ".." => {
1018 segments.pop();
1019 trailing_slash = n == last;
1020 }
1021 s => {
1022 segments.push(s);
1023 trailing_slash = false;
1024 }
1025 }
1026 }
1027 let mut out = String::with_capacity(decoded.len() + query.len());
1028 out.push('/');
1029 out.push_str(&segments.join("/"));
1030 if trailing_slash && !segments.is_empty() {
1031 out.push('/');
1032 }
1033 out.push_str(query);
1034 if out == target {
1035 std::borrow::Cow::Borrowed(target)
1036 } else {
1037 std::borrow::Cow::Owned(out)
1038 }
1039}
1040
1041fn host_matches(expected: &str, actual: &str) -> bool {
1044 let actual = if expected.contains(':') {
1045 actual
1046 } else if let Some(rest) = actual.strip_prefix('[') {
1047 rest.split(']').next().unwrap_or(rest)
1049 } else {
1050 actual.rsplit_once(':').map_or(actual, |(host, port)| if port.bytes().all(|b| b.is_ascii_digit()) { host } else { actual })
1051 };
1052 let expected = expected.trim_start_matches('[').trim_end_matches(']');
1053 actual.eq_ignore_ascii_case(expected)
1054}
1055
1056impl Application for ConfigDrivenApp {
1057 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1058 let request = normalized(request);
1059 let generation = self.current();
1060 match &generation.entry {
1061 Some(entry) => entry.execute(&request, conn),
1062 None => generation.dispatch.execute(&request, conn),
1063 }
1064 }
1065
1066 fn runs_inline(&self, request: &Request, conn: &ConnectionInfo) -> bool {
1070 let request = normalized(request);
1071 let generation = self.current();
1072 generation.entry.is_none()
1073 && !generation.dispatch.routes.iter().any(|r| r.matcher.matches(&request, conn))
1074 && App::is_inline_request(&request)
1075 }
1076
1077 #[cfg(feature = "http2")]
1081 fn execute_async<'a>(&'a self, request: &'a Request, conn: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
1082 let generation = self.current();
1083 if generation.entry.is_some() {
1084 return None;
1085 }
1086 let request = normalized(request);
1087 let route = generation.dispatch.routes.iter().find(|r| r.matcher.matches(&request, conn))?;
1088 let proxy = Arc::clone(route.async_proxy.as_ref()?);
1089 Some(Box::pin(async move { proxy.execute_async(&request, conn).await }))
1090 }
1091}
1092
1093#[derive(Clone, Copy)]
1098pub(crate) struct NullApp;
1099
1100impl Application for NullApp {
1101 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1102 let mut r = Response::new();
1103 r.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1104 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1105 Ok(r)
1106 }
1107}
1108
1109use std::collections::HashMap;
1112use std::collections::hash_map::DefaultHasher;
1113use std::hash::{Hash, Hasher};
1114use std::sync::atomic::AtomicUsize;
1115use std::time::{Duration, SystemTime, UNIX_EPOCH};
1116
1117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1121pub(crate) enum LoadBalanceStrategy {
1122 RoundRobin,
1123 Random,
1124 IpHash,
1125 LeastConnections,
1126}
1127
1128impl LoadBalanceStrategy {
1129 fn parse(s: &str) -> Self {
1130 match s {
1131 "random" => LoadBalanceStrategy::Random,
1132 "ip_hash" => LoadBalanceStrategy::IpHash,
1133 "least_connections" => LoadBalanceStrategy::LeastConnections,
1134 _ => LoadBalanceStrategy::RoundRobin,
1135 }
1136 }
1137}
1138
1139#[derive(Clone)]
1145pub(crate) struct DynamicProxy {
1146 live: Arc<RwLock<Vec<String>>>,
1147 counter: Arc<AtomicUsize>,
1148 connect_timeout: Duration,
1149 read_timeout: Duration,
1150 strip_prefix: Option<Arc<String>>,
1151 add_prefix: Option<Arc<String>>,
1152 tls: bool,
1153 strategy: LoadBalanceStrategy,
1154 connections: Arc<RwLock<HashMap<String, Arc<AtomicUsize>>>>,
1155}
1156
1157impl DynamicProxy {
1158 pub(crate) fn new(
1159 live: Arc<RwLock<Vec<String>>>,
1160 connect_timeout_ms: u64,
1161 read_timeout_ms: u64,
1162 strip_prefix: Option<String>,
1163 add_prefix: Option<String>,
1164 tls: bool,
1165 strategy: String,
1166 ) -> Self {
1167 DynamicProxy {
1168 live,
1169 counter: Arc::new(AtomicUsize::new(0)),
1170 connect_timeout: Duration::from_millis(connect_timeout_ms),
1171 read_timeout: Duration::from_millis(read_timeout_ms),
1172 strip_prefix: strip_prefix.map(Arc::new),
1173 add_prefix: add_prefix.map(Arc::new),
1174 tls,
1175 strategy: LoadBalanceStrategy::parse(&strategy),
1176 connections: Arc::new(RwLock::new(HashMap::new())),
1177 }
1178 }
1179
1180 fn next_backend(&self, client_ip: &str) -> Option<String> {
1181 let live = self.live.read().unwrap();
1182 if live.is_empty() {
1183 return None;
1184 }
1185
1186 let idx = match self.strategy {
1187 LoadBalanceStrategy::RoundRobin => {
1188 self.counter.fetch_add(1, Ordering::Relaxed) % live.len()
1189 }
1190 LoadBalanceStrategy::Random => {
1191 let nanos = SystemTime::now()
1192 .duration_since(UNIX_EPOCH)
1193 .map(|d| d.subsec_nanos())
1194 .unwrap_or(0) as usize;
1195 let salt = self.counter.fetch_add(1, Ordering::Relaxed);
1196 nanos.wrapping_add(salt) % live.len()
1197 }
1198 LoadBalanceStrategy::IpHash => {
1199 let mut hasher = DefaultHasher::new();
1200 client_ip.hash(&mut hasher);
1201 (hasher.finish() as usize) % live.len()
1202 }
1203 LoadBalanceStrategy::LeastConnections => {
1204 let connections = self.connections.read().unwrap();
1205 live.iter()
1206 .enumerate()
1207 .min_by_key(|(_, backend)| {
1208 connections
1209 .get(*backend)
1210 .map(|c| c.load(Ordering::Relaxed))
1211 .unwrap_or(0)
1212 })
1213 .map(|(i, _)| i)
1214 .unwrap_or(0)
1215 }
1216 };
1217
1218 Some(live[idx].clone())
1219 }
1220
1221 fn connection_counter(&self, backend: &str) -> Arc<AtomicUsize> {
1224 if let Some(counter) = self.connections.read().unwrap().get(backend) {
1225 return Arc::clone(counter);
1226 }
1227 let mut connections = self.connections.write().unwrap();
1228 Arc::clone(
1229 connections
1230 .entry(backend.to_string())
1231 .or_insert_with(|| Arc::new(AtomicUsize::new(0))),
1232 )
1233 }
1234}
1235
1236struct ConnectionGuard {
1239 counter: Arc<AtomicUsize>,
1240}
1241
1242impl Drop for ConnectionGuard {
1243 fn drop(&mut self) {
1244 self.counter.fetch_sub(1, Ordering::Relaxed);
1245 }
1246}
1247
1248struct Prepared<'a> {
1251 host: String,
1252 port: u16,
1253 request: std::borrow::Cow<'a, Request>,
1254 _guard: Option<ConnectionGuard>,
1255}
1256
1257impl DynamicProxy {
1258 fn prepare<'a>(&self, request: &'a Request, conn: &ConnectionInfo) -> Result<Prepared<'a>, Response> {
1261 let backend = self.next_backend(&conn.client.ip).ok_or_else(bad_gateway)?;
1262
1263 let guard = if self.strategy == LoadBalanceStrategy::LeastConnections {
1264 let counter = self.connection_counter(&backend);
1265 counter.fetch_add(1, Ordering::Relaxed);
1266 Some(ConnectionGuard { counter })
1267 } else {
1268 None
1269 };
1270
1271 let (host, port, _) = crate::proxy_config::health::parse_backend_url(&backend).ok_or_else(bad_gateway)?;
1272
1273 let request = if self.strip_prefix.is_some() || self.add_prefix.is_some() {
1275 let mut req_clone = request.clone();
1276 if let Some(ref sp) = self.strip_prefix {
1277 if let Some(stripped) = req_clone.request_uri.strip_prefix(sp.as_str()) {
1278 req_clone.request_uri = if stripped.is_empty() || !stripped.starts_with('/') {
1279 format!("/{}", stripped)
1280 } else {
1281 stripped.to_string()
1282 };
1283 }
1284 }
1285 if let Some(ref ap) = self.add_prefix {
1286 req_clone.request_uri = format!("{}{}", ap, req_clone.request_uri);
1287 }
1288 std::borrow::Cow::Owned(req_clone)
1289 } else {
1290 std::borrow::Cow::Borrowed(request)
1291 };
1292
1293 Ok(Prepared { host, port, request, _guard: guard })
1294 }
1295
1296 #[cfg(feature = "http2")]
1299 pub(crate) async fn execute_async(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1300 let prepared = match self.prepare(request, conn) {
1301 Ok(p) => p,
1302 Err(response) => return Ok(response),
1303 };
1304 let result = crate::proxy::async_upstream::forward(
1305 &prepared.request,
1306 &conn.client.ip,
1307 &prepared.host,
1308 prepared.port,
1309 self.connect_timeout,
1310 self.read_timeout,
1311 )
1312 .await;
1313 Ok(result.unwrap_or_else(|_| bad_gateway()))
1314 }
1315
1316 pub(crate) fn supports_async(&self) -> bool {
1318 cfg!(feature = "http2") && !self.tls
1319 }
1320}
1321
1322impl Application for DynamicProxy {
1323 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
1324 let prepared = match self.prepare(request, conn) {
1325 Ok(p) => p,
1326 Err(response) => return Ok(response),
1327 };
1328
1329 let result = crate::proxy::forward_upstream(
1332 crate::proxy::shared_pool(),
1333 &prepared.request,
1334 &conn.client.ip,
1335 &prepared.host,
1336 prepared.port,
1337 self.tls,
1338 self.connect_timeout,
1339 self.read_timeout,
1340 );
1341
1342 match result {
1343 Ok(r) => Ok(r),
1344 Err(_) => Ok(bad_gateway()),
1345 }
1346 }
1347}
1348
1349fn bad_gateway() -> Response {
1350 use crate::mime_type::MimeType;
1351 use crate::range::Range;
1352 let cr = Range::get_content_range(
1353 b"502 Bad Gateway".to_vec(),
1354 MimeType::TEXT_PLAIN.to_string(),
1355 );
1356 let mut r = Response::new();
1357 r.status_code = *STATUS_CODE_REASON_PHRASE.n502_bad_gateway.status_code;
1358 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n502_bad_gateway.reason_phrase.to_string();
1359 r.content_range_list = vec![cr];
1360 r
1361}
1362
1363#[derive(Clone)]
1369pub(crate) struct RedirectAdapter {
1370 location_template: Arc<String>,
1371 status: i16,
1372 reason: Arc<String>,
1373}
1374
1375impl RedirectAdapter {
1376 pub(crate) fn new(location: String, status: u16) -> Self {
1377 let (code, reason) = redirect_status(status);
1378 RedirectAdapter {
1379 location_template: Arc::new(location),
1380 status: code,
1381 reason: Arc::new(reason),
1382 }
1383 }
1384}
1385
1386fn redirect_status(code: u16) -> (i16, String) {
1387 let phrase = match code {
1388 301 => STATUS_CODE_REASON_PHRASE.n301_moved_permanently.reason_phrase,
1389 302 => STATUS_CODE_REASON_PHRASE.n302_found.reason_phrase,
1390 307 => STATUS_CODE_REASON_PHRASE.n307_temporary_redirect.reason_phrase,
1391 308 => STATUS_CODE_REASON_PHRASE.n308_permanent_redirect.reason_phrase,
1392 _ => "Redirect",
1393 };
1394 (code as i16, phrase.to_string())
1395}
1396
1397impl Application for RedirectAdapter {
1398 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1399 let location = self
1400 .location_template
1401 .replace("$path", &request.request_uri);
1402 use crate::header::Header;
1403 let mut r = Response::new();
1404 r.status_code = self.status;
1405 r.reason_phrase = self.reason.as_ref().clone();
1406 r.headers.push(Header { name: "Location".to_string(), value: location });
1407 Ok(r)
1408 }
1409}
1410
1411#[derive(Clone)]
1415pub(crate) struct RespondAdapter {
1416 status: i16,
1417 reason: Arc<String>,
1418 body: Arc<Vec<u8>>,
1419 content_type: Arc<String>,
1420}
1421
1422impl RespondAdapter {
1423 pub(crate) fn new(status: u16, body: String, content_type: String) -> Self {
1424 use crate::response::STATUS_CODE_REASON_PHRASE;
1425 let reason = match status {
1426 200 => STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
1427 201 => STATUS_CODE_REASON_PHRASE.n201_created.reason_phrase.to_string(),
1428 204 => STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string(),
1429 400 => STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string(),
1430 401 => STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string(),
1431 403 => STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string(),
1432 404 => STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string(),
1433 500 => STATUS_CODE_REASON_PHRASE.n500_internal_server_error.reason_phrase.to_string(),
1434 _ => "OK".to_string(),
1435 };
1436 RespondAdapter {
1437 status: status as i16,
1438 reason: Arc::new(reason),
1439 body: Arc::new(body.into_bytes()),
1440 content_type: Arc::new(content_type),
1441 }
1442 }
1443}
1444
1445impl Application for RespondAdapter {
1446 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1447 use crate::range::Range;
1448 let mut r = Response::new();
1449 r.status_code = self.status;
1450 r.reason_phrase = self.reason.as_ref().clone();
1451 if !self.body.is_empty() {
1452 r.content_range_list = vec![Range::get_content_range(
1453 self.body.as_ref().clone(),
1454 self.content_type.as_ref().clone(),
1455 )];
1456 }
1457 Ok(r)
1458 }
1459}
1460
1461#[derive(Clone)]
1470pub(crate) struct StaticAdapter {
1471 root: Arc<std::path::PathBuf>,
1472 index: Arc<Vec<String>>,
1473}
1474
1475impl StaticAdapter {
1476 pub(crate) fn new(root: String, index: Vec<String>) -> Self {
1477 let index = if index.is_empty() { vec!["index.html".to_string()] } else { index };
1478 StaticAdapter {
1479 root: Arc::new(std::path::PathBuf::from(root)),
1480 index: Arc::new(index),
1481 }
1482 }
1483
1484 fn resolve(&self, request_uri: &str) -> Option<std::path::PathBuf> {
1488 let raw_path = request_uri.split('?').next().unwrap_or(request_uri);
1489 let decoded = crate::url::URL::percent_decode(raw_path);
1490
1491 if decoded.split('/').any(|segment| segment == "..") {
1492 return None;
1493 }
1494
1495 let relative = decoded.trim_start_matches('/');
1496 Some(self.root.join(relative))
1497 }
1498}
1499
1500impl Application for StaticAdapter {
1501 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
1502 let mut response = Response::new();
1503
1504 let not_found = |mut response: Response| {
1505 response.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
1506 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
1507 response
1508 };
1509
1510 let candidate = match self.resolve(&request.request_uri) {
1511 Some(p) => p,
1512 None => {
1513 response.status_code = *STATUS_CODE_REASON_PHRASE.n403_forbidden.status_code;
1514 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string();
1515 return Ok(response);
1516 }
1517 };
1518
1519 let mut file_path = candidate;
1520 if file_path.is_dir() {
1521 let indexed = self
1522 .index
1523 .iter()
1524 .map(|name| file_path.join(name))
1525 .find(|p| p.is_file());
1526
1527 file_path = match indexed {
1528 Some(p) => p,
1529 None => return Ok(not_found(response)),
1530 };
1531 }
1532
1533 if !file_path.is_file() {
1534 return Ok(not_found(response));
1535 }
1536
1537 if let (Ok(root_canon), Ok(file_canon)) =
1540 (self.root.canonicalize(), file_path.canonicalize())
1541 {
1542 if !file_canon.starts_with(&root_canon) {
1543 return Ok(not_found(response));
1544 }
1545 }
1546
1547 let path_str = match file_path.to_str() {
1548 Some(s) => s,
1549 None => return Ok(not_found(response)),
1550 };
1551
1552 match crate::range::Range::get_content_range_of_a_file(path_str) {
1553 Ok(content_range) => {
1554 response.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
1555 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
1556 response.content_range_list = vec![content_range];
1557 Ok(response)
1558 }
1559 Err(_) => Ok(not_found(response)),
1560 }
1561 }
1562}
1563
1564pub(crate) struct PerRouteRateLimit(pub(crate) Arc<crate::rate_limit::RateLimiter>);
1568
1569impl crate::middleware::Middleware for PerRouteRateLimit {
1570 fn handle(
1571 &self,
1572 request: &Request,
1573 conn: &ConnectionInfo,
1574 next: &dyn Application,
1575 ) -> Result<Response, String> {
1576 use crate::error::{AppError, IntoResponse};
1577 if self.0.check(&conn.client.ip) {
1578 next.execute(request, conn)
1579 } else {
1580 let mut response = AppError::TooManyRequests.into_response();
1581 let retry_after = self.0.retry_after(&conn.client.ip);
1582 response.headers.push(Header {
1583 name: Header::_RETRY_AFTER.to_string(),
1584 value: retry_after.to_string(),
1585 });
1586 Ok(response)
1587 }
1588 }
1589}
1590
1591pub(crate) struct PerRouteMaxBodySize(pub(crate) u64);
1606
1607impl crate::middleware::Middleware for PerRouteMaxBodySize {
1608 fn handle(
1609 &self,
1610 request: &Request,
1611 conn: &ConnectionInfo,
1612 next: &dyn Application,
1613 ) -> Result<Response, String> {
1614 use crate::error::{AppError, IntoResponse};
1615 if request.body.len() as u64 > self.0 {
1616 Ok(AppError::PayloadTooLarge(format!(
1617 "request body of {} bytes exceeds this route's {}-byte limit",
1618 request.body.len(),
1619 self.0,
1620 ))
1621 .into_response())
1622 } else {
1623 next.execute(request, conn)
1624 }
1625 }
1626}
1627
1628pub(crate) struct BearerAuthMiddleware {
1632 pub(crate) token: Arc<String>,
1633}
1634
1635impl crate::middleware::Middleware for BearerAuthMiddleware {
1636 fn handle(
1637 &self,
1638 request: &Request,
1639 conn: &ConnectionInfo,
1640 next: &dyn Application,
1641 ) -> Result<Response, String> {
1642 use crate::error::{AppError, IntoResponse};
1643 let expected = format!("Bearer {}", self.token);
1644 let authorized = request
1645 .headers
1646 .iter()
1647 .any(|h| h.name.eq_ignore_ascii_case("authorization") && constant_time_eq(h.value.as_bytes(), expected.as_bytes()));
1648 if authorized {
1649 next.execute(request, conn)
1650 } else {
1651 Ok(AppError::Unauthorized.into_response())
1652 }
1653 }
1654}
1655
1656pub(crate) fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
1659 if a.len() != b.len() {
1660 return false;
1661 }
1662 a.iter().zip(b).fold(0u8, |diff, (x, y)| diff | (x ^ y)) == 0
1663}
1664
1665pub(crate) struct AuthMisconfigured;
1668
1669impl crate::middleware::Middleware for AuthMisconfigured {
1670 fn handle(
1671 &self,
1672 _request: &Request,
1673 _conn: &ConnectionInfo,
1674 _next: &dyn Application,
1675 ) -> Result<Response, String> {
1676 Ok(Response::text(
1677 STATUS_CODE_REASON_PHRASE.n503_service_unavailable,
1678 "503 Service Unavailable: authentication for this route is misconfigured",
1679 ))
1680 }
1681}
1682
1683pub(crate) fn arc_app<A: Application + Send + Sync + 'static>(
1687 a: A,
1688) -> Arc<dyn Application + Send + Sync> {
1689 Arc::new(a)
1690}
1691
1692pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
1697 builder::build_from_file()
1698}