1pub mod parser;
29pub mod health;
30pub mod builder;
31
32#[cfg(test)]
33mod tests;
34
35use std::sync::Arc;
36
37use crate::app::App;
38use crate::application::Application;
39use crate::core::New;
40use crate::request::Request;
41use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
42use crate::server::ConnectionInfo;
43
44#[derive(Debug, Clone)]
47pub struct ProxyConfig {
48 pub upstreams: Vec<UpstreamConfig>,
49 pub routes: Vec<RouteConfig>,
50 pub tcp_proxies: Vec<TcpProxyConfig>,
51 pub udp_proxies: Vec<UdpProxyConfig>,
52 pub ws_proxies: Vec<WsProxyConfig>,
53 pub global_middleware: MiddlewareConfig,
54}
55
56#[derive(Debug, Clone)]
57pub struct UpstreamConfig {
58 pub name: String,
59 pub backends: Vec<String>,
60 pub strategy: String, pub health_check: Option<HealthCheckConfig>,
62}
63
64#[derive(Debug, Clone)]
65pub struct HealthCheckConfig {
66 pub path: String,
67 pub interval_secs: u64,
68 pub timeout_ms: u64,
69 pub healthy_threshold: u32,
70 pub unhealthy_threshold: u32,
71}
72
73#[derive(Debug, Clone)]
74pub struct RouteConfig {
75 pub name: String,
76 pub match_: MatchConfig,
77 pub action: ActionConfig,
78 pub middleware: MiddlewareConfig,
79}
80
81#[derive(Debug, Clone, Default)]
82pub struct MatchConfig {
83 pub host: Option<String>,
84 pub path: Option<String>,
85 pub method: Option<String>,
86 pub content_type: Option<String>,
87}
88
89#[derive(Debug, Clone)]
90pub enum ActionConfig {
91 Proxy {
92 upstream: String,
93 connect_timeout_ms: u64,
94 read_timeout_ms: u64,
95 strip_path_prefix: Option<String>,
96 add_path_prefix: Option<String>,
97 },
98 Grpc {
99 upstream: String,
100 connect_timeout_ms: u64,
101 read_timeout_ms: u64,
102 },
103 Static {
104 root: String,
105 index: Vec<String>,
106 },
107 Redirect {
108 location: String,
109 status: u16,
110 },
111 Respond {
112 status: u16,
113 body: String,
114 content_type: String,
115 },
116 Mcp,
117 Unknown(String),
118}
119
120#[derive(Debug, Clone, Default)]
121pub struct MiddlewareConfig {
122 pub rate_limit: Option<RateLimitConfig>,
123 pub cache: Option<CacheConfig>,
124 pub auth: Option<AuthConfig>,
125 pub rewrite_request: Vec<RewriteRuleConfig>,
126 pub rewrite_response: Vec<RewriteRuleConfig>,
127 pub ip_allow: Vec<String>,
128 pub ip_deny: Vec<String>,
129}
130
131#[derive(Debug, Clone)]
132pub struct RateLimitConfig {
133 pub max_requests: u32,
134 pub window_secs: u64,
135}
136
137#[derive(Debug, Clone)]
138pub struct CacheConfig {
139 pub ttl_secs: u64,
140 pub vary_by: Vec<String>,
141}
142
143#[derive(Debug, Clone)]
144pub enum AuthConfig {
145 Basic { users_file: String },
146 Jwt { secret_env: String },
147 Bearer { token_env: String },
148}
149
150#[derive(Debug, Clone, Default)]
151pub struct RewriteRuleConfig {
152 pub type_: String,
153 pub name: Option<String>,
154 pub value: Option<String>,
155 pub prefix: Option<String>,
156 pub from: Option<String>,
157 pub to: Option<String>,
158 pub code: Option<u16>,
159 pub reason: Option<String>,
160}
161
162#[derive(Debug, Clone)]
163pub struct TcpProxyConfig {
164 pub name: String,
165 pub listen: String,
166 pub backends: Vec<String>,
167 pub connect_timeout_ms: u64,
168}
169
170#[derive(Debug, Clone)]
171pub struct UdpProxyConfig {
172 pub name: String,
173 pub listen: String,
174 pub backends: Vec<String>,
175 pub reply_timeout_ms: u64,
176 pub buffer_size: usize,
177}
178
179#[derive(Debug, Clone)]
180pub struct WsProxyConfig {
181 pub name: String,
182 pub listen: String,
183 pub backends: Vec<String>,
184 pub connect_timeout_ms: u64,
185 pub read_timeout_ms: u64,
186}
187
188impl ProxyConfig {
191 pub fn is_proxy_mode() -> bool {
195 let path = config_file_path();
196 match std::fs::read_to_string(&path) {
197 Ok(contents) => {
198 contents.contains("[[route]]") || contents.contains("[[upstream]]")
199 }
200 Err(_) => false,
201 }
202 }
203
204 pub fn load() -> Self {
206 let path = config_file_path();
207 let contents = std::fs::read_to_string(&path).unwrap_or_default();
208 Self::from_str(&contents)
209 }
210
211 pub fn from_str(toml: &str) -> Self {
213 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
214
215 let map = parser::parse(toml);
216
217 let mut upstreams = Vec::new();
219 let mut i = 0;
220 loop {
221 let sec = format!("upstream[{}]", i);
222 if !section_exists(&map, &sec) {
223 break;
224 }
225 let name = get_str(&map, &sec, "name");
226 let backends = get_array(&map, &sec, "backends");
227 let strategy = {
228 let s = get_str(&map, &sec, "strategy");
229 if s.is_empty() { "round_robin".to_string() } else { s }
230 };
231 let hc_sec = format!("{}.health_check", sec);
232 let health_check = if section_exists(&map, &hc_sec) {
233 Some(HealthCheckConfig {
234 path: {
235 let p = get_str(&map, &hc_sec, "path");
236 if p.is_empty() { "/health".to_string() } else { p }
237 },
238 interval_secs: get_u64(&map, &hc_sec, "interval_secs", 30),
239 timeout_ms: get_u64(&map, &hc_sec, "timeout_ms", 5000),
240 healthy_threshold: get_u32(&map, &hc_sec, "healthy_threshold", 2),
241 unhealthy_threshold: get_u32(&map, &hc_sec, "unhealthy_threshold", 3),
242 })
243 } else {
244 None
245 };
246 upstreams.push(UpstreamConfig { name, backends, strategy, health_check });
247 i += 1;
248 }
249
250 let mut routes = Vec::new();
252 let mut i = 0;
253 loop {
254 let sec = format!("route[{}]", i);
255 if !section_exists(&map, &sec) {
256 break;
257 }
258 let name = get_str(&map, &sec, "name");
259
260 let m_sec = format!("{}.match", sec);
262 let match_ = MatchConfig {
263 host: {
264 let h = get_str(&map, &m_sec, "host");
265 if h.is_empty() { None } else { Some(h) }
266 },
267 path: {
268 let p = get_str(&map, &m_sec, "path");
269 if p.is_empty() { None } else { Some(p) }
270 },
271 method: {
272 let m = get_str(&map, &m_sec, "method");
273 if m.is_empty() { None } else { Some(m.to_uppercase()) }
274 },
275 content_type: {
276 let c = get_str(&map, &m_sec, "content_type");
277 if c.is_empty() { None } else { Some(c) }
278 },
279 };
280
281 let a_sec = format!("{}.action", sec);
283 let action_type = get_str(&map, &a_sec, "type");
284 let action = match action_type.as_str() {
285 "proxy" => {
286 let p_sec = format!("{}.action.proxy", sec);
287 ActionConfig::Proxy {
288 upstream: get_str(&map, &p_sec, "upstream"),
289 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
290 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
291 strip_path_prefix: {
292 let v = get_str(&map, &p_sec, "strip_path_prefix");
293 if v.is_empty() { None } else { Some(v) }
294 },
295 add_path_prefix: {
296 let v = get_str(&map, &p_sec, "add_path_prefix");
297 if v.is_empty() { None } else { Some(v) }
298 },
299 }
300 }
301 "grpc" => {
302 let p_sec = format!("{}.action.grpc", sec);
303 ActionConfig::Grpc {
304 upstream: get_str(&map, &p_sec, "upstream"),
305 connect_timeout_ms: get_u64(&map, &p_sec, "connect_timeout_ms", 5000),
306 read_timeout_ms: get_u64(&map, &p_sec, "read_timeout_ms", 30000),
307 }
308 }
309 "static" => {
310 let s_sec = format!("{}.action.static", sec);
311 ActionConfig::Static {
312 root: get_str(&map, &s_sec, "root"),
313 index: get_array(&map, &s_sec, "index"),
314 }
315 }
316 "redirect" => {
317 let r_sec = format!("{}.action.redirect", sec);
318 ActionConfig::Redirect {
319 location: get_str(&map, &r_sec, "location"),
320 status: get_u64(&map, &r_sec, "status", 301) as u16,
321 }
322 }
323 "respond" => {
324 let r_sec = format!("{}.action.respond", sec);
325 ActionConfig::Respond {
326 status: get_u64(&map, &r_sec, "status", 200) as u16,
327 body: get_str(&map, &r_sec, "body"),
328 content_type: {
329 let ct = get_str(&map, &r_sec, "content_type");
330 if ct.is_empty() { "text/plain".to_string() } else { ct }
331 },
332 }
333 }
334 "mcp" => ActionConfig::Mcp,
335 other => ActionConfig::Unknown(other.to_string()),
336 };
337
338 let mw_sec = format!("{}.middleware", sec);
340 let middleware = parse_middleware_config(&map, &mw_sec, i);
341
342 routes.push(RouteConfig { name, match_, action, middleware });
343 i += 1;
344 }
345
346 let mut tcp_proxies = Vec::new();
348 let mut i = 0;
349 loop {
350 let sec = format!("tcp_proxy[{}]", i);
351 if !section_exists(&map, &sec) {
352 break;
353 }
354 tcp_proxies.push(TcpProxyConfig {
355 name: get_str(&map, &sec, "name"),
356 listen: get_str(&map, &sec, "listen"),
357 backends: get_array(&map, &sec, "backends"),
358 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
359 });
360 i += 1;
361 }
362
363 let mut udp_proxies = Vec::new();
365 let mut i = 0;
366 loop {
367 let sec = format!("udp_proxy[{}]", i);
368 if !section_exists(&map, &sec) {
369 break;
370 }
371 udp_proxies.push(UdpProxyConfig {
372 name: get_str(&map, &sec, "name"),
373 listen: get_str(&map, &sec, "listen"),
374 backends: get_array(&map, &sec, "backends"),
375 reply_timeout_ms: get_u64(&map, &sec, "reply_timeout_ms", 5000),
376 buffer_size: get_u64(&map, &sec, "buffer_size", 65536) as usize,
377 });
378 i += 1;
379 }
380
381 let mut ws_proxies = Vec::new();
383 let mut i = 0;
384 loop {
385 let sec = format!("ws_proxy[{}]", i);
386 if !section_exists(&map, &sec) {
387 break;
388 }
389 ws_proxies.push(WsProxyConfig {
390 name: get_str(&map, &sec, "name"),
391 listen: get_str(&map, &sec, "listen"),
392 backends: get_array(&map, &sec, "backends"),
393 connect_timeout_ms: get_u64(&map, &sec, "connect_timeout_ms", 5000),
394 read_timeout_ms: get_u64(&map, &sec, "read_timeout_ms", 30000),
395 });
396 i += 1;
397 }
398
399 let global_middleware = parse_middleware_config(&map, "middleware", usize::MAX);
401
402 ProxyConfig {
403 upstreams,
404 routes,
405 tcp_proxies,
406 udp_proxies,
407 ws_proxies,
408 global_middleware,
409 }
410 }
411}
412
413fn parse_middleware_config(
416 map: &parser::SectionMap,
417 mw_sec: &str,
418 route_idx: usize,
419) -> MiddlewareConfig {
420 use parser::{get_array, get_str, get_u32, get_u64, section_exists};
421
422 let rl_sec = format!("{}.rate_limit", mw_sec);
423 let rate_limit = if section_exists(map, &rl_sec) {
424 Some(RateLimitConfig {
425 max_requests: get_u32(map, &rl_sec, "max_requests", 1000),
426 window_secs: get_u64(map, &rl_sec, "window_secs", 60),
427 })
428 } else {
429 None
430 };
431
432 let c_sec = format!("{}.cache", mw_sec);
433 let cache = if section_exists(map, &c_sec) {
434 Some(CacheConfig {
435 ttl_secs: get_u64(map, &c_sec, "ttl_secs", 60),
436 vary_by: get_array(map, &c_sec, "vary_by"),
437 })
438 } else {
439 None
440 };
441
442 let a_sec = format!("{}.auth", mw_sec);
443 let auth = if section_exists(map, &a_sec) {
444 let auth_type = get_str(map, &a_sec, "type");
445 match auth_type.as_str() {
446 "bearer" => Some(AuthConfig::Bearer {
447 token_env: get_str(map, &a_sec, "token_env"),
448 }),
449 "jwt" => Some(AuthConfig::Jwt {
450 secret_env: get_str(map, &a_sec, "secret_env"),
451 }),
452 "basic" => Some(AuthConfig::Basic {
453 users_file: get_str(map, &a_sec, "users_file"),
454 }),
455 _ => None,
456 }
457 } else {
458 None
459 };
460
461 let rewrite_request = collect_rewrite_rules(map, mw_sec, "request");
467 let rewrite_response = collect_rewrite_rules(map, mw_sec, "response");
468
469 let ip_sec = format!("{}.ip_filter", mw_sec);
470 let ip_allow = if section_exists(map, &ip_sec) {
471 get_array(map, &ip_sec, "allow")
472 } else {
473 vec![]
474 };
475 let ip_deny = if section_exists(map, &ip_sec) {
476 get_array(map, &ip_sec, "deny")
477 } else {
478 vec![]
479 };
480
481 let _ = route_idx; MiddlewareConfig { rate_limit, cache, auth, rewrite_request, rewrite_response, ip_allow, ip_deny }
484}
485
486fn collect_rewrite_rules(
488 map: &parser::SectionMap,
489 mw_sec: &str,
490 direction: &str,
491) -> Vec<RewriteRuleConfig> {
492 use parser::{get_str, get_u64};
493
494 let mut rules = Vec::new();
495 let mut j = 0;
496 loop {
497 let rsec = format!("{}.rewrite.{}[{}]", mw_sec, direction, j);
498 if !parser::section_exists(map, &rsec) {
499 break;
500 }
501 let code_val = get_u64(map, &rsec, "code", 0);
502 rules.push(RewriteRuleConfig {
503 type_: get_str(map, &rsec, "type"),
504 name: {
505 let v = get_str(map, &rsec, "name");
506 if v.is_empty() { None } else { Some(v) }
507 },
508 value: {
509 let v = get_str(map, &rsec, "value");
510 if v.is_empty() { None } else { Some(v) }
511 },
512 prefix: {
513 let v = get_str(map, &rsec, "prefix");
514 if v.is_empty() { None } else { Some(v) }
515 },
516 from: {
517 let v = get_str(map, &rsec, "from");
518 if v.is_empty() { None } else { Some(v) }
519 },
520 to: {
521 let v = get_str(map, &rsec, "to");
522 if v.is_empty() { None } else { Some(v) }
523 },
524 code: if code_val == 0 { None } else { Some(code_val as u16) },
525 reason: {
526 let v = get_str(map, &rsec, "reason");
527 if v.is_empty() { None } else { Some(v) }
528 },
529 });
530 j += 1;
531 }
532 rules
533}
534
535fn config_file_path() -> String {
536 std::env::var("RWS_CONFIG_FILE").unwrap_or_else(|_| "rws.config.toml".to_string())
537}
538
539pub(crate) struct CompiledRoute {
543 pub(crate) matcher: RouteMatcher,
544 pub(crate) handler: Arc<dyn Application + Send + Sync>,
546}
547
548#[derive(Clone, Default)]
550pub(crate) struct RouteMatcher {
551 pub(crate) host: Option<String>,
553 pub(crate) path_prefix: Option<String>,
555 pub(crate) path_exact: Option<String>,
557 pub(crate) method: Option<String>,
559 pub(crate) content_type_prefix: Option<String>,
561}
562
563impl RouteMatcher {
564 pub(crate) fn from_match_config(cfg: &MatchConfig) -> Self {
565 let (path_prefix, path_exact) = match &cfg.path {
566 Some(p) if p.ends_with('*') => {
567 let stripped = p.trim_end_matches('*').to_string();
569 (Some(stripped), None)
570 }
571 Some(p) => (None, Some(p.clone())),
572 None => (None, None),
573 };
574 let content_type_prefix = cfg.content_type.as_ref().map(|ct| {
575 if ct.ends_with('*') {
576 ct.trim_end_matches('*').to_string()
577 } else {
578 ct.clone()
579 }
580 });
581 RouteMatcher {
582 host: cfg.host.clone(),
583 path_prefix,
584 path_exact,
585 method: cfg.method.clone(),
586 content_type_prefix,
587 }
588 }
589
590 pub(crate) fn matches(&self, request: &Request, conn: &ConnectionInfo) -> bool {
592 if let Some(ref expected_host) = self.host {
594 let actual_host = conn
595 .sni_hostname
596 .as_deref()
597 .or_else(|| {
598 request
599 .headers
600 .iter()
601 .find(|h| h.name.eq_ignore_ascii_case("host"))
602 .map(|h| h.value.as_str())
603 })
604 .unwrap_or("");
605 if actual_host != expected_host.as_str() {
606 return false;
607 }
608 }
609
610 if let Some(ref m) = self.method {
612 if request.method.to_uppercase() != m.as_str() {
613 return false;
614 }
615 }
616
617 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
619 if let Some(ref prefix) = self.path_prefix {
620 if !path.starts_with(prefix.as_str()) {
621 return false;
622 }
623 } else if let Some(ref exact) = self.path_exact {
624 if path != exact.as_str() {
625 return false;
626 }
627 }
628
629 if let Some(ref ct_prefix) = self.content_type_prefix {
631 let actual_ct = request
632 .headers
633 .iter()
634 .find(|h| h.name.eq_ignore_ascii_case("content-type"))
635 .map(|h| h.value.as_str())
636 .unwrap_or("");
637 if !actual_ct.starts_with(ct_prefix.as_str()) {
638 return false;
639 }
640 }
641
642 true
643 }
644}
645
646#[derive(Clone)]
651pub struct ConfigDrivenApp {
652 routes: Arc<Vec<CompiledRoute>>,
653 fallback: App,
656}
657
658impl ConfigDrivenApp {
659 pub(crate) fn new(routes: Vec<CompiledRoute>) -> Self {
660 use crate::core::New;
661 ConfigDrivenApp {
662 routes: Arc::new(routes),
663 fallback: App::new(),
664 }
665 }
666}
667
668impl Application for ConfigDrivenApp {
669 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
670 for route in self.routes.iter() {
671 if route.matcher.matches(request, conn) {
672 return route.handler.execute(request, conn);
673 }
674 }
675 self.fallback.execute(request, conn)
676 }
677}
678
679#[derive(Clone, Copy)]
684pub(crate) struct NullApp;
685
686impl Application for NullApp {
687 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
688 let mut r = Response::new();
689 r.status_code = *STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
690 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string();
691 Ok(r)
692 }
693}
694
695use std::sync::atomic::{AtomicUsize, Ordering};
698use std::sync::RwLock;
699use std::time::Duration;
700
701#[derive(Clone)]
707pub(crate) struct DynamicProxy {
708 live: Arc<RwLock<Vec<String>>>,
709 counter: Arc<AtomicUsize>,
710 connect_timeout: Duration,
711 read_timeout: Duration,
712 strip_prefix: Option<Arc<String>>,
713 add_prefix: Option<Arc<String>>,
714}
715
716impl DynamicProxy {
717 pub(crate) fn new(
718 live: Arc<RwLock<Vec<String>>>,
719 connect_timeout_ms: u64,
720 read_timeout_ms: u64,
721 strip_prefix: Option<String>,
722 add_prefix: Option<String>,
723 ) -> Self {
724 DynamicProxy {
725 live,
726 counter: Arc::new(AtomicUsize::new(0)),
727 connect_timeout: Duration::from_millis(connect_timeout_ms),
728 read_timeout: Duration::from_millis(read_timeout_ms),
729 strip_prefix: strip_prefix.map(Arc::new),
730 add_prefix: add_prefix.map(Arc::new),
731 }
732 }
733
734 fn next_backend(&self) -> Option<String> {
735 let live = self.live.read().unwrap();
736 if live.is_empty() {
737 return None;
738 }
739 let idx = self.counter.fetch_add(1, Ordering::Relaxed) % live.len();
740 Some(live[idx].clone())
741 }
742}
743
744impl Application for DynamicProxy {
745 fn execute(&self, request: &Request, conn: &ConnectionInfo) -> Result<Response, String> {
746 let backend = match self.next_backend() {
747 Some(b) => b,
748 None => {
749 return Ok(bad_gateway());
750 }
751 };
752
753 let (host, port) = match crate::proxy_config::health::parse_host_port(&backend) {
754 Some(hp) => hp,
755 None => return Ok(bad_gateway()),
756 };
757
758 let mut req_clone;
760 let effective_request = if self.strip_prefix.is_some() || self.add_prefix.is_some() {
761 req_clone = request.clone();
762 if let Some(ref sp) = self.strip_prefix {
763 if let Some(stripped) = req_clone.request_uri.strip_prefix(sp.as_str()) {
764 req_clone.request_uri = if stripped.is_empty() || !stripped.starts_with('/') {
765 format!("/{}", stripped)
766 } else {
767 stripped.to_string()
768 };
769 }
770 }
771 if let Some(ref ap) = self.add_prefix {
772 req_clone.request_uri = format!("{}{}", ap, req_clone.request_uri);
773 }
774 &req_clone
775 } else {
776 request
777 };
778
779 match crate::proxy::proxy_http1(
780 effective_request,
781 &conn.client.ip,
782 &host,
783 port,
784 self.connect_timeout,
785 self.read_timeout,
786 ) {
787 Ok(r) => Ok(r),
788 Err(_) => Ok(bad_gateway()),
789 }
790 }
791}
792
793fn bad_gateway() -> Response {
794 use crate::mime_type::MimeType;
795 use crate::range::Range;
796 let cr = Range::get_content_range(
797 b"502 Bad Gateway".to_vec(),
798 MimeType::TEXT_PLAIN.to_string(),
799 );
800 let mut r = Response::new();
801 r.status_code = *STATUS_CODE_REASON_PHRASE.n502_bad_gateway.status_code;
802 r.reason_phrase = STATUS_CODE_REASON_PHRASE.n502_bad_gateway.reason_phrase.to_string();
803 r.content_range_list = vec![cr];
804 r
805}
806
807#[derive(Clone)]
813pub(crate) struct RedirectAdapter {
814 location_template: Arc<String>,
815 status: i16,
816 reason: Arc<String>,
817}
818
819impl RedirectAdapter {
820 pub(crate) fn new(location: String, status: u16) -> Self {
821 let (code, reason) = redirect_status(status);
822 RedirectAdapter {
823 location_template: Arc::new(location),
824 status: code,
825 reason: Arc::new(reason),
826 }
827 }
828}
829
830fn redirect_status(code: u16) -> (i16, String) {
831 let phrase = match code {
832 301 => STATUS_CODE_REASON_PHRASE.n301_moved_permanently.reason_phrase,
833 302 => STATUS_CODE_REASON_PHRASE.n302_found.reason_phrase,
834 307 => STATUS_CODE_REASON_PHRASE.n307_temporary_redirect.reason_phrase,
835 308 => STATUS_CODE_REASON_PHRASE.n308_permanent_redirect.reason_phrase,
836 _ => "Redirect",
837 };
838 (code as i16, phrase.to_string())
839}
840
841impl Application for RedirectAdapter {
842 fn execute(&self, request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
843 let location = self
844 .location_template
845 .replace("$path", &request.request_uri);
846 use crate::header::Header;
847 let mut r = Response::new();
848 r.status_code = self.status;
849 r.reason_phrase = self.reason.as_ref().clone();
850 r.headers.push(Header { name: "Location".to_string(), value: location });
851 Ok(r)
852 }
853}
854
855#[derive(Clone)]
859pub(crate) struct RespondAdapter {
860 status: i16,
861 reason: Arc<String>,
862 body: Arc<Vec<u8>>,
863 content_type: Arc<String>,
864}
865
866impl RespondAdapter {
867 pub(crate) fn new(status: u16, body: String, content_type: String) -> Self {
868 use crate::response::STATUS_CODE_REASON_PHRASE;
869 let reason = match status {
870 200 => STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
871 201 => STATUS_CODE_REASON_PHRASE.n201_created.reason_phrase.to_string(),
872 204 => STATUS_CODE_REASON_PHRASE.n204_no_content.reason_phrase.to_string(),
873 400 => STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase.to_string(),
874 401 => STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string(),
875 403 => STATUS_CODE_REASON_PHRASE.n403_forbidden.reason_phrase.to_string(),
876 404 => STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase.to_string(),
877 500 => STATUS_CODE_REASON_PHRASE.n500_internal_server_error.reason_phrase.to_string(),
878 _ => "OK".to_string(),
879 };
880 RespondAdapter {
881 status: status as i16,
882 reason: Arc::new(reason),
883 body: Arc::new(body.into_bytes()),
884 content_type: Arc::new(content_type),
885 }
886 }
887}
888
889impl Application for RespondAdapter {
890 fn execute(&self, _request: &Request, _conn: &ConnectionInfo) -> Result<Response, String> {
891 use crate::range::Range;
892 let mut r = Response::new();
893 r.status_code = self.status;
894 r.reason_phrase = self.reason.as_ref().clone();
895 if !self.body.is_empty() {
896 r.content_range_list = vec![Range::get_content_range(
897 self.body.as_ref().clone(),
898 self.content_type.as_ref().clone(),
899 )];
900 }
901 Ok(r)
902 }
903}
904
905pub(crate) struct PerRouteRateLimit(pub(crate) Arc<crate::rate_limit::RateLimiter>);
909
910impl crate::middleware::Middleware for PerRouteRateLimit {
911 fn handle(
912 &self,
913 request: &Request,
914 conn: &ConnectionInfo,
915 next: &dyn Application,
916 ) -> Result<Response, String> {
917 use crate::error::{AppError, IntoResponse};
918 if self.0.check(&conn.client.ip) {
919 next.execute(request, conn)
920 } else {
921 Ok(AppError::TooManyRequests.into_response())
922 }
923 }
924}
925
926pub(crate) struct BearerAuthMiddleware {
930 pub(crate) token: Arc<String>,
931}
932
933impl crate::middleware::Middleware for BearerAuthMiddleware {
934 fn handle(
935 &self,
936 request: &Request,
937 conn: &ConnectionInfo,
938 next: &dyn Application,
939 ) -> Result<Response, String> {
940 use crate::error::{AppError, IntoResponse};
941 let expected = format!("Bearer {}", self.token);
942 let authorized = request
943 .headers
944 .iter()
945 .any(|h| h.name.eq_ignore_ascii_case("authorization") && h.value == expected);
946 if authorized {
947 next.execute(request, conn)
948 } else {
949 Ok(AppError::Unauthorized.into_response())
950 }
951 }
952}
953
954pub(crate) fn arc_app<A: Application + Send + Sync + 'static>(
958 a: A,
959) -> Arc<dyn Application + Send + Sync> {
960 Arc::new(a)
961}
962
963pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
968 builder::build_from_file()
969}