rust_web_server/proxy_config/
builder.rs1use 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
61 for upstream in &config.upstreams {
62 let live = Arc::new(RwLock::new(upstream.backends.clone()));
63 upstream_lives.insert(upstream.name.clone(), Arc::clone(&live));
64
65 let client = upstream.tls_client_cert_file.as_deref().zip(upstream.tls_client_key_file.as_deref());
66 let configured = upstream.tls_ca_file.is_some() || upstream.tls_client_cert_file.is_some() || upstream.tls_client_key_file.is_some();
67 let trust = configured.then(|| {
68 Arc::new(crate::proxy::UpstreamTls::new(upstream.tls_ca_file.as_deref(), client).unwrap_or_else(|e| {
69 eprintln!("[proxy_config] ERROR: upstream '{}': {e} — its TLS backends answer 502 until this is fixed", upstream.name);
70 crate::proxy::UpstreamTls::reject_all(&upstream.name)
71 }))
72 });
73 if let Some(trust) = &trust {
74 upstream_trust.insert(upstream.name.clone(), Arc::clone(trust));
75 }
76
77 if let Some(ref hc) = upstream.health_check {
78 crate::proxy_config::health::start_health_checker(
79 upstream.name.clone(),
80 upstream.backends.clone(),
81 Arc::clone(&live),
82 hc.clone(),
83 Arc::clone(health_stop),
84 trust,
85 );
86 }
87 }
88
89 let mut compiled: Vec<CompiledRoute> = Vec::new();
91
92 for route in &config.routes {
93 let matcher = RouteMatcher::from_match_config(&route.match_);
94
95 let mut async_proxy: Option<Arc<DynamicProxy>> = None;
96
97 let base_handler: Arc<dyn crate::application::Application + Send + Sync> =
99 match &route.action {
100 ActionConfig::Proxy {
101 upstream,
102 connect_timeout_ms,
103 read_timeout_ms,
104 strip_path_prefix,
105 add_path_prefix,
106 } => {
107 let live = upstream_lives
108 .get(upstream.as_str())
109 .cloned()
110 .unwrap_or_else(|| {
111 Arc::new(RwLock::new(vec![upstream.clone()]))
113 });
114 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
115 let upstream_tls = upstream_cfg.map_or_else(|| upstream.starts_with("https://"), |u| u.tls);
116 let upstream_strategy = upstream_cfg
117 .map(|u| u.strategy.clone())
118 .unwrap_or_else(|| "round_robin".to_string());
119 let proxy = Arc::new(DynamicProxy::new(
120 live,
121 *connect_timeout_ms,
122 *read_timeout_ms,
123 strip_path_prefix.clone(),
124 add_path_prefix.clone(),
125 upstream_tls,
126 upstream_strategy,
127 ).with_trust(upstream_trust.get(upstream.as_str()).cloned()));
128 if proxy.supports_async() && route.middleware.is_empty() {
129 async_proxy = Some(Arc::clone(&proxy));
130 }
131 proxy
132 }
133
134 ActionConfig::Redirect { location, status } => {
135 arc_app(RedirectAdapter::new(location.clone(), *status))
136 }
137
138 ActionConfig::Respond { status, body, content_type } => {
139 arc_app(RespondAdapter::new(*status, body.clone(), content_type.clone()))
140 }
141
142 ActionConfig::Static { root, index } => {
143 arc_app(StaticAdapter::new(root.clone(), index.clone()))
144 }
145
146 ActionConfig::Grpc { upstream, connect_timeout_ms, read_timeout_ms } => {
149 let live = upstream_lives
150 .get(upstream.as_str())
151 .cloned()
152 .unwrap_or_else(|| Arc::new(RwLock::new(vec![upstream.clone()])));
153 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
154 let upstream_tls = upstream_cfg.map_or_else(|| upstream.starts_with("https://"), |u| u.tls);
155 #[cfg(feature = "http2")]
156 {
157 let _ = upstream_cfg;
158 arc_app(crate::proxy_config::H2Route::new(
159 live,
160 upstream_tls,
161 upstream_trust.get(upstream.as_str()).cloned(),
162 *connect_timeout_ms,
163 *read_timeout_ms,
164 ))
165 }
166 #[cfg(not(feature = "http2"))]
167 {
168 let upstream_strategy = upstream_cfg
169 .map(|u| u.strategy.clone())
170 .unwrap_or_else(|| "round_robin".to_string());
171 arc_app(
172 DynamicProxy::new(live, *connect_timeout_ms, *read_timeout_ms, None, None, upstream_tls, upstream_strategy)
173 .with_trust(upstream_trust.get(upstream.as_str()).cloned()),
174 )
175 }
176 }
177
178 ActionConfig::Mcp | ActionConfig::Unknown(_) => {
179 arc_app(crate::proxy_config::NullApp)
181 }
182 };
183
184 let handler = apply_middleware(base_handler, &route.middleware);
186
187 compiled.push(CompiledRoute { matcher, handler, async_proxy });
188 }
189 compiled
190}
191
192fn spawn_listeners(config: &ProxyConfig) -> Vec<std::thread::JoinHandle<()>> {
196 let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
197
198 for tcp_cfg in &config.tcp_proxies {
199 let listen = tcp_cfg.listen.clone();
200 let backends = tcp_cfg.backends.clone();
201 let timeout_ms = tcp_cfg.connect_timeout_ms;
202 let name = tcp_cfg.name.clone();
203 let h = std::thread::Builder::new()
204 .name(format!("tcp-proxy-{}", name))
205 .spawn(move || {
206 let proxy = crate::tcp_proxy::TcpProxy::new(backends)
207 .connect_timeout_ms(timeout_ms);
208 if let Err(e) = proxy.bind(&listen) {
209 eprintln!("[tcp_proxy:{}] {}", name, e);
210 }
211 })
212 .expect("failed to spawn tcp proxy thread");
213 handles.push(h);
214 }
215
216 for udp_cfg in &config.udp_proxies {
217 let listen = udp_cfg.listen.clone();
218 let backends = udp_cfg.backends.clone();
219 let reply_timeout_ms = udp_cfg.reply_timeout_ms;
220 let buffer_size = udp_cfg.buffer_size;
221 let name = udp_cfg.name.clone();
222 let h = std::thread::Builder::new()
223 .name(format!("udp-proxy-{}", name))
224 .spawn(move || {
225 let proxy = crate::udp_proxy::UdpProxy::new(backends)
226 .reply_timeout_ms(reply_timeout_ms)
227 .buffer_size(buffer_size);
228 if let Err(e) = proxy.bind(&listen) {
229 eprintln!("[udp_proxy:{}] {}", name, e);
230 }
231 })
232 .expect("failed to spawn udp proxy thread");
233 handles.push(h);
234 }
235
236 for ws_cfg in &config.ws_proxies {
237 let listen = ws_cfg.listen.clone();
238 let backends = ws_cfg.backends.clone();
239 let connect_timeout_ms = ws_cfg.connect_timeout_ms;
240 let read_timeout_ms = ws_cfg.read_timeout_ms;
241 let name = ws_cfg.name.clone();
242
243 let live = Arc::new(RwLock::new(backends.clone()));
247 if let Some(ref hc) = ws_cfg.health_check {
248 crate::proxy_config::health::start_health_checker(
249 name.clone(),
250 backends.clone(),
251 Arc::clone(&live),
252 hc.clone(),
253 Arc::new(AtomicBool::new(false)),
254 None,
255 );
256 }
257
258 let h = std::thread::Builder::new()
259 .name(format!("ws-proxy-{}", name))
260 .spawn(move || {
261 let proxy = crate::ws_proxy::WsProxy::with_live_backends(backends, live)
262 .connect_timeout_ms(connect_timeout_ms)
263 .read_timeout_ms(read_timeout_ms);
264 if let Err(e) = proxy.bind(&listen) {
265 eprintln!("[ws_proxy:{}] {}", name, e);
266 }
267 })
268 .expect("failed to spawn ws proxy thread");
269 handles.push(h);
270 }
271
272 handles
273}
274
275pub(crate) fn apply_middleware(
289 handler: Arc<dyn crate::application::Application + Send + Sync>,
290 mw: &MiddlewareConfig,
291) -> Arc<dyn crate::application::Application + Send + Sync> {
292 let mut app: Arc<dyn crate::application::Application + Send + Sync> = handler;
295
296 if let Some(ref cache_cfg) = mw.cache {
298 let mut layer = crate::cache::CacheLayer::memory(1000).ttl(cache_cfg.ttl_secs);
299 for vh in &cache_cfg.vary_by {
300 layer = layer.vary_by_header(vh.as_str());
301 }
302 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
303 }
304
305 if !mw.rewrite_request.is_empty() || !mw.rewrite_response.is_empty() {
307 let mut layer = crate::rewrite::RewriteLayer::new();
308 for rule in &mw.rewrite_request {
309 layer = apply_request_rewrite_rule(layer, rule);
310 }
311 for rule in &mw.rewrite_response {
312 layer = apply_response_rewrite_rule(layer, rule);
313 }
314 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
315 }
316
317 if let Some(ref auth_cfg) = mw.auth {
322 let deny = |reason: String| -> Arc<dyn crate::application::Application + Send + Sync> {
323 eprintln!(
324 "[proxy_config] ERROR: {reason} — this route answers 503 until the auth configuration is fixed."
325 );
326 arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(AuthMisconfigured))
327 };
328 app = match auth_cfg {
329 AuthConfig::Bearer { token_env } => {
330 let token = std::env::var(token_env).unwrap_or_default();
331 if token.is_empty() {
332 deny(format!("bearer auth: env var '{token_env}' is unset or empty"))
333 } else {
334 arc_app(
335 WithMiddleware::new(ArcApp(Arc::clone(&app)))
336 .wrap(BearerAuthMiddleware { token: Arc::new(token) }),
337 )
338 }
339 }
340 #[cfg(feature = "auth")]
341 AuthConfig::Jwt { secret_env } => {
342 let secret = std::env::var(secret_env).unwrap_or_default();
343 if secret.is_empty() {
344 deny(format!("JWT auth: env var '{secret_env}' is unset or empty"))
345 } else {
346 arc_app(
347 WithMiddleware::new(ArcApp(Arc::clone(&app)))
348 .wrap(crate::auth::JwtLayer::new(secret)),
349 )
350 }
351 }
352 #[cfg(not(feature = "auth"))]
353 AuthConfig::Jwt { .. } => {
354 deny("JWT auth requires the 'auth' feature, which this build doesn't include".to_string())
355 }
356 #[cfg(feature = "auth")]
357 AuthConfig::Basic { htpasswd_file } => {
358 match crate::auth::BasicAuthLayer::from_htpasswd_file(htpasswd_file) {
359 Ok(layer) => arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer)),
360 Err(e) => deny(format!("basic auth: {e}")),
361 }
362 }
363 #[cfg(not(feature = "auth"))]
364 AuthConfig::Basic { .. } => {
365 deny("basic auth requires the 'auth' feature, which this build doesn't include".to_string())
366 }
367 };
368 }
369
370 if let Some(ref rl_cfg) = mw.rate_limit {
372 let limiter = Arc::new(crate::rate_limit::RateLimiter::new(
373 rl_cfg.max_requests,
374 rl_cfg.window_secs,
375 ));
376 app = arc_app(
377 WithMiddleware::new(ArcApp(Arc::clone(&app)))
378 .wrap(PerRouteRateLimit(limiter)),
379 );
380 }
381
382 if !mw.ip_allow.is_empty() {
384 let filter = crate::ip_filter::IpFilter::allow(mw.ip_allow.iter().map(|s| s.as_str()));
385 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
386 } else if !mw.ip_deny.is_empty() {
387 let filter = crate::ip_filter::IpFilter::deny(mw.ip_deny.iter().map(|s| s.as_str()));
388 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
389 }
390
391 if let Some(timeout_ms) = mw.timeout_ms {
394 app = arc_app(crate::timeout::TimeoutLayer::from_arc(
395 app,
396 std::time::Duration::from_millis(timeout_ms),
397 ));
398 }
399
400 if let Some(max_body_size) = mw.max_body_size {
403 app = arc_app(
404 WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(PerRouteMaxBodySize(max_body_size)),
405 );
406 }
407
408 app
409}
410
411fn apply_request_rewrite_rule(
412 layer: crate::rewrite::RewriteLayer,
413 rule: &crate::proxy_config::RewriteRuleConfig,
414) -> crate::rewrite::RewriteLayer {
415 match rule.type_.as_str() {
416 "header_set" => {
417 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
418 return layer.request_header_set(name, value);
419 }
420 }
421 "header_remove" => {
422 if let Some(name) = &rule.name {
423 return layer.request_header_remove(name);
424 }
425 }
426 "uri_set" => {
427 if let Some(value) = &rule.value {
428 return layer.request_uri_set(value);
429 }
430 }
431 "uri_strip_prefix" | "strip_prefix" => {
432 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
433 return layer.request_uri_strip_prefix(prefix);
434 }
435 }
436 "uri_add_prefix" | "add_prefix" => {
437 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
438 return layer.request_uri_add_prefix(prefix);
439 }
440 }
441 _ => {}
442 }
443 layer
444}
445
446fn apply_response_rewrite_rule(
447 layer: crate::rewrite::RewriteLayer,
448 rule: &crate::proxy_config::RewriteRuleConfig,
449) -> crate::rewrite::RewriteLayer {
450 match rule.type_.as_str() {
451 "header_set" => {
452 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
453 return layer.response_header_set(name, value);
454 }
455 }
456 "header_remove" => {
457 if let Some(name) = &rule.name {
458 return layer.response_header_remove(name);
459 }
460 }
461 "status" => {
462 if let (Some(code), Some(reason)) = (&rule.code, &rule.reason) {
463 return layer.response_status(*code as i16, reason);
464 }
465 }
466 "body_replace" => {
467 if let (Some(from), Some(to)) = (&rule.from, &rule.to) {
468 return layer.response_body_replace(from, to);
469 }
470 }
471 _ => {}
472 }
473 layer
474}
475
476struct ArcApp(Arc<dyn crate::application::Application + Send + Sync>);
481
482impl crate::application::Application for ArcApp {
483 fn execute(
484 &self,
485 request: &crate::request::Request,
486 conn: &crate::server::ConnectionInfo,
487 ) -> Result<crate::response::Response, String> {
488 self.0.execute(request, conn)
489 }
490
491 fn streams_body(&self, request: &crate::request::Request, conn: &crate::server::ConnectionInfo) -> bool {
492 self.0.streams_body(request, conn)
493 }
494}
495
496impl Clone for ArcApp {
497 fn clone(&self) -> Self {
498 ArcApp(Arc::clone(&self.0))
499 }
500}