1use std::collections::HashMap;
5use std::sync::atomic::AtomicBool;
6use std::sync::{Arc, RwLock};
7
8use crate::middleware::WithMiddleware;
9use crate::proxy_config::{
10 ActionConfig, AuthConfig, CompiledRoute, ConfigDrivenApp, DynamicProxy, MiddlewareConfig,
11 ProxyConfig, RedirectAdapter, RespondAdapter, RouteMatcher, StaticAdapter, arc_app,
12 AuthMisconfigured, BearerAuthMiddleware, PerRouteMaxBodySize, PerRouteRateLimit,
13};
14
15pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
22 let source = std::fs::read_to_string(crate::proxy_config::config_file_path()).unwrap_or_default();
23 let config = ProxyConfig::from_str(&source);
24 let mut report = crate::config_check::ConfigReport::default();
25 config.validate(&mut report);
26 for e in &report.errors {
27 eprintln!("[proxy_config] ERROR: {e}");
28 }
29 for w in &report.warnings {
30 eprintln!("[proxy_config] Warning: {w}");
31 }
32 let (app, handles) = build_with_source(config, Some(source));
33 let reloadable = app.clone();
34 crate::config_reload::on_reload(move || reloadable.reload_from_file());
35 (app, handles)
36}
37
38pub fn build(config: ProxyConfig) -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
40 build_with_source(config, None)
41}
42
43fn build_with_source(config: ProxyConfig, source: Option<String>) -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
44 let health_stop = Arc::new(AtomicBool::new(false));
45 let compiled = compile_routes(&config, &health_stop);
46 let handles = spawn_listeners(&config);
47 (ConfigDrivenApp::new(compiled, config, health_stop, source), handles)
48}
49
50pub(crate) fn compile_routes(config: &ProxyConfig, health_stop: &Arc<AtomicBool>) -> Vec<CompiledRoute> {
54 let mut upstream_lives: HashMap<String, Arc<RwLock<Vec<String>>>> = HashMap::new();
56 let mut upstream_trust: HashMap<String, Arc<crate::proxy::UpstreamTls>> = HashMap::new();
60 let mut upstream_policies: HashMap<String, Arc<crate::proxy_config::upstream::UpstreamPolicy>> = HashMap::new();
63
64 for upstream in &config.upstreams {
65 let live = Arc::new(RwLock::new(upstream.backends.clone()));
66 upstream_lives.insert(upstream.name.clone(), Arc::clone(&live));
67 upstream_policies.insert(
68 upstream.name.clone(),
69 Arc::new(crate::proxy_config::upstream::UpstreamPolicy::new(
70 &upstream.name,
71 Arc::clone(&live),
72 &upstream.backends,
73 &upstream.weights,
74 upstream.sticky.clone(),
75 upstream.circuit_breaker.as_ref(),
76 upstream.retry.clone(),
77 )),
78 );
79
80 let client = upstream.tls_client_cert_file.as_deref().zip(upstream.tls_client_key_file.as_deref());
81 let configured = upstream.tls_ca_file.is_some() || upstream.tls_client_cert_file.is_some() || upstream.tls_client_key_file.is_some();
82 let trust = configured.then(|| {
83 Arc::new(crate::proxy::UpstreamTls::new(upstream.tls_ca_file.as_deref(), client).unwrap_or_else(|e| {
84 eprintln!("[proxy_config] ERROR: upstream '{}': {e} — its TLS backends answer 502 until this is fixed", upstream.name);
85 crate::proxy::UpstreamTls::reject_all(&upstream.name)
86 }))
87 });
88 if let Some(trust) = &trust {
89 upstream_trust.insert(upstream.name.clone(), Arc::clone(trust));
90 }
91
92 if let Some(ref hc) = upstream.health_check {
93 crate::proxy_config::health::start_health_checker(
94 upstream.name.clone(),
95 upstream.backends.clone(),
96 Arc::clone(&live),
97 hc.clone(),
98 Arc::clone(health_stop),
99 trust,
100 );
101 }
102 }
103
104 crate::proxy_config::upstream::register(upstream_policies.values().cloned().collect());
105
106 let mut caches: Vec<(String, crate::cache::CacheLayer)> = Vec::new();
108 let mut compiled: Vec<CompiledRoute> = Vec::new();
109
110 for route in &config.routes {
111 let matcher = RouteMatcher::from_match_config(&route.match_);
112
113 let mut async_proxy: Option<Arc<DynamicProxy>> = None;
114 let cache_name = format!("route:{}", if route.name.is_empty() { format!("#{}", compiled.len() + 1) } else { route.name.clone() });
115 let mirror = route.middleware.mirror.as_ref().map(|m| build_mirror(&route.name, m, &upstream_lives));
116
117 let base_handler: Arc<dyn crate::application::Application + Send + Sync> =
119 match &route.action {
120 ActionConfig::Proxy {
121 upstream,
122 connect_timeout_ms,
123 read_timeout_ms,
124 strip_path_prefix,
125 add_path_prefix,
126 } => {
127 let live = upstream_lives
128 .get(upstream.as_str())
129 .cloned()
130 .unwrap_or_else(|| {
131 Arc::new(RwLock::new(vec![upstream.clone()]))
133 });
134 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
135 let upstream_tls = upstream_cfg.map_or_else(|| upstream.starts_with("https://"), |u| u.tls);
136 let upstream_strategy = upstream_cfg
137 .map(|u| u.strategy.clone())
138 .unwrap_or_else(|| "round_robin".to_string());
139 let proxy = Arc::new(DynamicProxy::new(
140 live,
141 *connect_timeout_ms,
142 *read_timeout_ms,
143 strip_path_prefix.clone(),
144 add_path_prefix.clone(),
145 upstream_tls,
146 upstream_strategy,
147 )
148 .with_trust(upstream_trust.get(upstream.as_str()).cloned())
149 .with_policy(upstream, upstream_policies.get(upstream.as_str()).cloned())
150 .with_mirror(mirror.clone())
151 .with_early_hints(&route.middleware.early_hints)
152 .with_h3(upstream_cfg.is_some_and(|u| u.protocol.eq_ignore_ascii_case("h3"))));
153 if proxy.supports_async() && route.middleware.is_empty() {
154 async_proxy = Some(Arc::clone(&proxy));
155 }
156 proxy
157 }
158
159 ActionConfig::Redirect { location, status } => {
160 arc_app(RedirectAdapter::new(location.clone(), *status))
161 }
162
163 ActionConfig::Respond { status, body, content_type } => {
164 arc_app(RespondAdapter::new(*status, body.clone(), content_type.clone()))
165 }
166
167 ActionConfig::Static { root, index } => {
168 arc_app(StaticAdapter::new(root.clone(), index.clone()))
169 }
170
171 ActionConfig::Grpc { upstream, connect_timeout_ms, read_timeout_ms } => {
174 let live = upstream_lives
175 .get(upstream.as_str())
176 .cloned()
177 .unwrap_or_else(|| Arc::new(RwLock::new(vec![upstream.clone()])));
178 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
179 let upstream_tls = upstream_cfg.map_or_else(|| upstream.starts_with("https://"), |u| u.tls);
180 #[cfg(feature = "http2")]
181 {
182 let _ = upstream_cfg;
183 arc_app(crate::proxy_config::H2Route::new(
184 live,
185 upstream_tls,
186 upstream_trust.get(upstream.as_str()).cloned(),
187 *connect_timeout_ms,
188 *read_timeout_ms,
189 ))
190 }
191 #[cfg(not(feature = "http2"))]
192 {
193 let upstream_strategy = upstream_cfg
194 .map(|u| u.strategy.clone())
195 .unwrap_or_else(|| "round_robin".to_string());
196 arc_app(
197 DynamicProxy::new(live, *connect_timeout_ms, *read_timeout_ms, None, None, upstream_tls, upstream_strategy)
198 .with_trust(upstream_trust.get(upstream.as_str()).cloned())
199 .with_policy(upstream, upstream_policies.get(upstream.as_str()).cloned()),
200 )
201 }
202 }
203
204 ActionConfig::Mcp(gateway) => arc_app(crate::proxy_config::mcp_gateway::GatewayAdapter::start(
205 (**gateway).clone(),
206 route.match_.path.clone().unwrap_or_else(|| "/mcp".to_string()),
207 )),
208 ActionConfig::Llm(llm) => arc_app(crate::proxy_config::llm::build(llm)),
209 ActionConfig::Unknown(_) => {
210 arc_app(crate::proxy_config::NullApp)
212 }
213 };
214
215 let mut handler = apply_middleware_named(base_handler, &route.middleware, &cache_name, &mut caches);
219 if !matches!(route.action, ActionConfig::Proxy { .. }) {
220 if let Some(mirror) = &mirror {
221 handler = arc_app(WithMiddleware::new(ArcApp(handler)).wrap(crate::proxy::mirror::MirrorLayer::new(mirror.clone())));
222 }
223 if !route.middleware.early_hints.is_empty() {
224 let layer = crate::early_hints::EarlyHintsLayer::new(route.middleware.early_hints.iter().cloned());
225 handler = arc_app(WithMiddleware::new(ArcApp(handler)).wrap(layer));
226 }
227 }
228
229 compiled.push(CompiledRoute { matcher, handler, async_proxy });
230 }
231 crate::cache::registry::replace_group("route:", caches);
232 compiled
233}
234
235fn spawn_listeners(config: &ProxyConfig) -> Vec<std::thread::JoinHandle<()>> {
239 let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
240
241 for tcp_cfg in &config.tcp_proxies {
242 let listen = tcp_cfg.listen.clone();
243 let backends = tcp_cfg.backends.clone();
244 let timeout_ms = tcp_cfg.connect_timeout_ms;
245 let name = tcp_cfg.name.clone();
246 let h = std::thread::Builder::new()
247 .name(format!("tcp-proxy-{}", name))
248 .spawn(move || {
249 let proxy = crate::tcp_proxy::TcpProxy::new(backends)
250 .connect_timeout_ms(timeout_ms);
251 if let Err(e) = proxy.bind(&listen) {
252 eprintln!("[tcp_proxy:{}] {}", name, e);
253 }
254 })
255 .expect("failed to spawn tcp proxy thread");
256 handles.push(h);
257 }
258
259 for udp_cfg in &config.udp_proxies {
260 let listen = udp_cfg.listen.clone();
261 let backends = udp_cfg.backends.clone();
262 let reply_timeout_ms = udp_cfg.reply_timeout_ms;
263 let buffer_size = udp_cfg.buffer_size;
264 let name = udp_cfg.name.clone();
265 let h = std::thread::Builder::new()
266 .name(format!("udp-proxy-{}", name))
267 .spawn(move || {
268 let proxy = crate::udp_proxy::UdpProxy::new(backends)
269 .reply_timeout_ms(reply_timeout_ms)
270 .buffer_size(buffer_size);
271 if let Err(e) = proxy.bind(&listen) {
272 eprintln!("[udp_proxy:{}] {}", name, e);
273 }
274 })
275 .expect("failed to spawn udp proxy thread");
276 handles.push(h);
277 }
278
279 for ws_cfg in &config.ws_proxies {
280 let listen = ws_cfg.listen.clone();
281 let backends = ws_cfg.backends.clone();
282 let connect_timeout_ms = ws_cfg.connect_timeout_ms;
283 let read_timeout_ms = ws_cfg.read_timeout_ms;
284 let name = ws_cfg.name.clone();
285
286 let live = Arc::new(RwLock::new(backends.clone()));
290 if let Some(ref hc) = ws_cfg.health_check {
291 crate::proxy_config::health::start_health_checker(
292 name.clone(),
293 backends.clone(),
294 Arc::clone(&live),
295 hc.clone(),
296 Arc::new(AtomicBool::new(false)),
297 None,
298 );
299 }
300
301 let h = std::thread::Builder::new()
302 .name(format!("ws-proxy-{}", name))
303 .spawn(move || {
304 let proxy = crate::ws_proxy::WsProxy::with_live_backends(backends, live)
305 .connect_timeout_ms(connect_timeout_ms)
306 .read_timeout_ms(read_timeout_ms);
307 if let Err(e) = proxy.bind(&listen) {
308 eprintln!("[ws_proxy:{}] {}", name, e);
309 }
310 })
311 .expect("failed to spawn ws proxy thread");
312 handles.push(h);
313 }
314
315 handles
316}
317
318fn build_mirror(route: &str, config: &crate::proxy_config::MirrorConfig, lives: &HashMap<String, Arc<RwLock<Vec<String>>>>) -> crate::proxy::mirror::Mirror {
333 let name = if config.upstream.is_empty() { format!("{route}-mirror") } else { config.upstream.clone() };
334 let mirror = match lives.get(&config.upstream) {
335 Some(live) if !config.upstream.is_empty() => crate::proxy::mirror::Mirror::with_live_backends(name, Arc::clone(live)),
336 _ if !config.upstream.is_empty() => crate::proxy::mirror::Mirror::new(name, vec![config.upstream.clone()]),
337 _ => crate::proxy::mirror::Mirror::new(name, config.backends.clone()),
338 };
339 mirror.percent(if config.percent.is_finite() { config.percent } else { 0.0 })
340}
341
342pub(crate) fn apply_middleware(
343 handler: Arc<dyn crate::application::Application + Send + Sync>,
344 mw: &MiddlewareConfig,
345) -> Arc<dyn crate::application::Application + Send + Sync> {
346 apply_middleware_named(handler, mw, "middleware", &mut Vec::new())
347}
348
349pub(crate) fn apply_middleware_named(
352 handler: Arc<dyn crate::application::Application + Send + Sync>,
353 mw: &MiddlewareConfig,
354 name: &str,
355 caches: &mut Vec<(String, crate::cache::CacheLayer)>,
356) -> Arc<dyn crate::application::Application + Send + Sync> {
357 let mut app: Arc<dyn crate::application::Application + Send + Sync> = handler;
360
361 if let Some(ref cache_cfg) = mw.cache {
363 let mut layer = if cache_cfg.dir.is_empty() {
364 crate::cache::CacheLayer::memory(1000)
365 } else {
366 let max = crate::proxy_config::cache_max_bytes(&cache_cfg.max_size).unwrap_or(256 * 1024 * 1024);
367 crate::cache::CacheLayer::disk(&cache_cfg.dir, max).unwrap_or_else(|e| {
368 eprintln!("[proxy_config] ERROR: cache dir {}: {e} — this route caches in memory instead", cache_cfg.dir);
369 crate::cache::CacheLayer::memory(1000)
370 })
371 }
372 .ttl(cache_cfg.ttl_secs);
373 if !cache_cfg.purge_token_env.is_empty() {
374 layer = layer.purge_token(std::env::var(&cache_cfg.purge_token_env).unwrap_or_default().trim());
375 }
376 caches.push((name.to_string(), layer.clone()));
377 for vh in &cache_cfg.vary_by {
378 layer = layer.vary_by_header(vh.as_str());
379 }
380 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
381 }
382
383 if !mw.rewrite_request.is_empty() || !mw.rewrite_response.is_empty() {
385 let mut layer = crate::rewrite::RewriteLayer::new();
386 for rule in &mw.rewrite_request {
387 layer = apply_request_rewrite_rule(layer, rule);
388 }
389 for rule in &mw.rewrite_response {
390 layer = apply_response_rewrite_rule(layer, rule);
391 }
392 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
393 }
394
395 if let Some(ref wasm) = mw.wasm {
397 #[cfg(feature = "wasm-plugins")]
398 let layer = crate::wasm_plugin::WasmLayer::from_file(&wasm.module)
399 .map(|l| l.fuel(wasm.fuel).memory_mb(wasm.memory_mb).config(wasm.config.clone()));
400 #[cfg(not(feature = "wasm-plugins"))]
401 let layer: Result<crate::proxy_config::PluginMisconfigured, String> =
402 Err(format!("plugin {}: this rws binary was built without the wasm-plugins feature", wasm.module));
403 app = match layer {
404 Ok(layer) => arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer)),
405 Err(e) => {
406 eprintln!("[proxy_config] ERROR: {e} — this route answers 503 until the plugin is fixed.");
407 arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(crate::proxy_config::PluginMisconfigured))
408 }
409 };
410 }
411
412 if let Some(ref auth_cfg) = mw.auth {
417 let deny = |reason: String| -> Arc<dyn crate::application::Application + Send + Sync> {
418 eprintln!(
419 "[proxy_config] ERROR: {reason} — this route answers 503 until the auth configuration is fixed."
420 );
421 arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(AuthMisconfigured))
422 };
423 app = match auth_cfg {
424 AuthConfig::Bearer { token_env } => {
425 let token = std::env::var(token_env).unwrap_or_default();
426 if token.is_empty() {
427 deny(format!("bearer auth: env var '{token_env}' is unset or empty"))
428 } else {
429 arc_app(
430 WithMiddleware::new(ArcApp(Arc::clone(&app)))
431 .wrap(BearerAuthMiddleware { token: Arc::new(token) }),
432 )
433 }
434 }
435 #[cfg(feature = "auth")]
436 AuthConfig::Jwt { secret_env } => {
437 let secret = std::env::var(secret_env).unwrap_or_default();
438 if secret.is_empty() {
439 deny(format!("JWT auth: env var '{secret_env}' is unset or empty"))
440 } else {
441 arc_app(
442 WithMiddleware::new(ArcApp(Arc::clone(&app)))
443 .wrap(crate::auth::JwtLayer::new(secret)),
444 )
445 }
446 }
447 #[cfg(not(feature = "auth"))]
448 AuthConfig::Jwt { .. } => {
449 deny("JWT auth requires the 'auth' feature, which this build doesn't include".to_string())
450 }
451 #[cfg(feature = "auth")]
452 AuthConfig::Basic { htpasswd_file } => {
453 match crate::auth::BasicAuthLayer::from_htpasswd_file(htpasswd_file) {
454 Ok(layer) => arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer)),
455 Err(e) => deny(format!("basic auth: {e}")),
456 }
457 }
458 #[cfg(not(feature = "auth"))]
459 AuthConfig::Basic { .. } => {
460 deny("basic auth requires the 'auth' feature, which this build doesn't include".to_string())
461 }
462 };
463 }
464
465 if let Some(ref rl_cfg) = mw.rate_limit {
467 let limiter = Arc::new(crate::rate_limit::RateLimiter::new(
468 rl_cfg.max_requests,
469 rl_cfg.window_secs,
470 ));
471 app = arc_app(
472 WithMiddleware::new(ArcApp(Arc::clone(&app)))
473 .wrap(PerRouteRateLimit(limiter)),
474 );
475 }
476
477 if !mw.ip_allow.is_empty() {
479 let filter = crate::ip_filter::IpFilter::allow(mw.ip_allow.iter().map(|s| s.as_str()));
480 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
481 } else if !mw.ip_deny.is_empty() {
482 let filter = crate::ip_filter::IpFilter::deny(mw.ip_deny.iter().map(|s| s.as_str()));
483 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
484 }
485
486 if let Some(timeout_ms) = mw.timeout_ms {
489 app = arc_app(crate::timeout::TimeoutLayer::from_arc(
490 app,
491 std::time::Duration::from_millis(timeout_ms),
492 ));
493 }
494
495 if let Some(max_body_size) = mw.max_body_size {
498 app = arc_app(
499 WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(PerRouteMaxBodySize(max_body_size)),
500 );
501 }
502
503 app
504}
505
506fn apply_request_rewrite_rule(
507 layer: crate::rewrite::RewriteLayer,
508 rule: &crate::proxy_config::RewriteRuleConfig,
509) -> crate::rewrite::RewriteLayer {
510 match rule.type_.as_str() {
511 "header_set" => {
512 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
513 return layer.request_header_set(name, value);
514 }
515 }
516 "header_remove" => {
517 if let Some(name) = &rule.name {
518 return layer.request_header_remove(name);
519 }
520 }
521 "uri_set" => {
522 if let Some(value) = &rule.value {
523 return layer.request_uri_set(value);
524 }
525 }
526 "uri_strip_prefix" | "strip_prefix" => {
527 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
528 return layer.request_uri_strip_prefix(prefix);
529 }
530 }
531 "uri_add_prefix" | "add_prefix" => {
532 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
533 return layer.request_uri_add_prefix(prefix);
534 }
535 }
536 _ => {}
537 }
538 layer
539}
540
541fn apply_response_rewrite_rule(
542 layer: crate::rewrite::RewriteLayer,
543 rule: &crate::proxy_config::RewriteRuleConfig,
544) -> crate::rewrite::RewriteLayer {
545 match rule.type_.as_str() {
546 "header_set" => {
547 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
548 return layer.response_header_set(name, value);
549 }
550 }
551 "header_remove" => {
552 if let Some(name) = &rule.name {
553 return layer.response_header_remove(name);
554 }
555 }
556 "status" => {
557 if let (Some(code), Some(reason)) = (&rule.code, &rule.reason) {
558 return layer.response_status(*code as i16, reason);
559 }
560 }
561 "body_replace" => {
562 if let (Some(from), Some(to)) = (&rule.from, &rule.to) {
563 return layer.response_body_replace(from, to);
564 }
565 }
566 _ => {}
567 }
568 layer
569}
570
571pub(crate) struct ArcApp(pub(crate) Arc<dyn crate::application::Application + Send + Sync>);
576
577impl crate::application::Application for ArcApp {
578 fn execute(
579 &self,
580 request: &crate::request::Request,
581 conn: &crate::server::ConnectionInfo,
582 ) -> Result<crate::response::Response, String> {
583 self.0.execute(request, conn)
584 }
585
586 fn streams_body(&self, request: &crate::request::Request, conn: &crate::server::ConnectionInfo) -> bool {
587 self.0.streams_body(request, conn)
588 }
589}
590
591impl Clone for ArcApp {
592 fn clone(&self) -> Self {
593 ArcApp(Arc::clone(&self.0))
594 }
595}