rust_web_server/proxy_config/
builder.rs1use std::collections::HashMap;
5use std::sync::{Arc, RwLock};
6
7use crate::middleware::WithMiddleware;
8use crate::proxy_config::{
9 ActionConfig, AuthConfig, CompiledRoute, ConfigDrivenApp, DynamicProxy, MiddlewareConfig,
10 ProxyConfig, RedirectAdapter, RespondAdapter, RouteMatcher, StaticAdapter, arc_app,
11 AuthMisconfigured, BearerAuthMiddleware, PerRouteMaxBodySize, PerRouteRateLimit,
12};
13
14pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
17 let config = ProxyConfig::load();
18 build(config)
19}
20
21pub fn build(config: ProxyConfig) -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
23 let mut upstream_lives: HashMap<String, Arc<RwLock<Vec<String>>>> = HashMap::new();
25
26 for upstream in &config.upstreams {
27 let live = Arc::new(RwLock::new(upstream.backends.clone()));
28 upstream_lives.insert(upstream.name.clone(), Arc::clone(&live));
29
30 if let Some(ref hc) = upstream.health_check {
31 crate::proxy_config::health::start_health_checker(
32 upstream.name.clone(),
33 upstream.backends.clone(),
34 Arc::clone(&live),
35 hc.clone(),
36 );
37 }
38 }
39
40 let mut compiled: Vec<CompiledRoute> = Vec::new();
42
43 for route in &config.routes {
44 let matcher = RouteMatcher::from_match_config(&route.match_);
45
46 let base_handler: Arc<dyn crate::application::Application + Send + Sync> =
48 match &route.action {
49 ActionConfig::Proxy {
50 upstream,
51 connect_timeout_ms,
52 read_timeout_ms,
53 strip_path_prefix,
54 add_path_prefix,
55 } => {
56 let live = upstream_lives
57 .get(upstream.as_str())
58 .cloned()
59 .unwrap_or_else(|| {
60 Arc::new(RwLock::new(vec![upstream.clone()]))
62 });
63 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
64 let upstream_tls = upstream_cfg.map(|u| u.tls).unwrap_or(false);
65 let upstream_strategy = upstream_cfg
66 .map(|u| u.strategy.clone())
67 .unwrap_or_else(|| "round_robin".to_string());
68 arc_app(DynamicProxy::new(
69 live,
70 *connect_timeout_ms,
71 *read_timeout_ms,
72 strip_path_prefix.clone(),
73 add_path_prefix.clone(),
74 upstream_tls,
75 upstream_strategy,
76 ))
77 }
78
79 ActionConfig::Redirect { location, status } => {
80 arc_app(RedirectAdapter::new(location.clone(), *status))
81 }
82
83 ActionConfig::Respond { status, body, content_type } => {
84 arc_app(RespondAdapter::new(*status, body.clone(), content_type.clone()))
85 }
86
87 ActionConfig::Static { root, index } => {
88 arc_app(StaticAdapter::new(root.clone(), index.clone()))
89 }
90
91 ActionConfig::Grpc { upstream, connect_timeout_ms, read_timeout_ms } => {
94 let live = upstream_lives
95 .get(upstream.as_str())
96 .cloned()
97 .unwrap_or_else(|| Arc::new(RwLock::new(vec![upstream.clone()])));
98 let upstream_cfg = config.upstreams.iter().find(|u| u.name == *upstream);
99 let upstream_tls = upstream_cfg.map(|u| u.tls).unwrap_or(false);
100 let upstream_strategy = upstream_cfg
101 .map(|u| u.strategy.clone())
102 .unwrap_or_else(|| "round_robin".to_string());
103 arc_app(DynamicProxy::new(live, *connect_timeout_ms, *read_timeout_ms, None, None, upstream_tls, upstream_strategy))
104 }
105
106 ActionConfig::Mcp | ActionConfig::Unknown(_) => {
107 arc_app(crate::proxy_config::NullApp)
109 }
110 };
111
112 let handler = apply_middleware(base_handler, &route.middleware);
114
115 compiled.push(CompiledRoute { matcher, handler });
116 }
117
118 let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
120
121 for tcp_cfg in &config.tcp_proxies {
122 let listen = tcp_cfg.listen.clone();
123 let backends = tcp_cfg.backends.clone();
124 let timeout_ms = tcp_cfg.connect_timeout_ms;
125 let name = tcp_cfg.name.clone();
126 let h = std::thread::Builder::new()
127 .name(format!("tcp-proxy-{}", name))
128 .spawn(move || {
129 let proxy = crate::tcp_proxy::TcpProxy::new(backends)
130 .connect_timeout_ms(timeout_ms);
131 if let Err(e) = proxy.bind(&listen) {
132 eprintln!("[tcp_proxy:{}] {}", name, e);
133 }
134 })
135 .expect("failed to spawn tcp proxy thread");
136 handles.push(h);
137 }
138
139 for udp_cfg in &config.udp_proxies {
140 let listen = udp_cfg.listen.clone();
141 let backends = udp_cfg.backends.clone();
142 let reply_timeout_ms = udp_cfg.reply_timeout_ms;
143 let buffer_size = udp_cfg.buffer_size;
144 let name = udp_cfg.name.clone();
145 let h = std::thread::Builder::new()
146 .name(format!("udp-proxy-{}", name))
147 .spawn(move || {
148 let proxy = crate::udp_proxy::UdpProxy::new(backends)
149 .reply_timeout_ms(reply_timeout_ms)
150 .buffer_size(buffer_size);
151 if let Err(e) = proxy.bind(&listen) {
152 eprintln!("[udp_proxy:{}] {}", name, e);
153 }
154 })
155 .expect("failed to spawn udp proxy thread");
156 handles.push(h);
157 }
158
159 for ws_cfg in &config.ws_proxies {
160 let listen = ws_cfg.listen.clone();
161 let backends = ws_cfg.backends.clone();
162 let connect_timeout_ms = ws_cfg.connect_timeout_ms;
163 let read_timeout_ms = ws_cfg.read_timeout_ms;
164 let name = ws_cfg.name.clone();
165
166 let live = Arc::new(RwLock::new(backends.clone()));
170 if let Some(ref hc) = ws_cfg.health_check {
171 crate::proxy_config::health::start_health_checker(
172 name.clone(),
173 backends.clone(),
174 Arc::clone(&live),
175 hc.clone(),
176 );
177 }
178
179 let h = std::thread::Builder::new()
180 .name(format!("ws-proxy-{}", name))
181 .spawn(move || {
182 let proxy = crate::ws_proxy::WsProxy::with_live_backends(backends, live)
183 .connect_timeout_ms(connect_timeout_ms)
184 .read_timeout_ms(read_timeout_ms);
185 if let Err(e) = proxy.bind(&listen) {
186 eprintln!("[ws_proxy:{}] {}", name, e);
187 }
188 })
189 .expect("failed to spawn ws proxy thread");
190 handles.push(h);
191 }
192
193 (ConfigDrivenApp::new(compiled), handles)
194}
195
196fn apply_middleware(
210 handler: Arc<dyn crate::application::Application + Send + Sync>,
211 mw: &MiddlewareConfig,
212) -> Arc<dyn crate::application::Application + Send + Sync> {
213 let mut app: Arc<dyn crate::application::Application + Send + Sync> = handler;
216
217 if let Some(ref cache_cfg) = mw.cache {
219 let mut layer = crate::cache::CacheLayer::memory(1000).ttl(cache_cfg.ttl_secs);
220 for vh in &cache_cfg.vary_by {
221 layer = layer.vary_by_header(vh.as_str());
222 }
223 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
224 }
225
226 if !mw.rewrite_request.is_empty() || !mw.rewrite_response.is_empty() {
228 let mut layer = crate::rewrite::RewriteLayer::new();
229 for rule in &mw.rewrite_request {
230 layer = apply_request_rewrite_rule(layer, rule);
231 }
232 for rule in &mw.rewrite_response {
233 layer = apply_response_rewrite_rule(layer, rule);
234 }
235 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
236 }
237
238 if let Some(ref auth_cfg) = mw.auth {
243 let deny = |reason: String| -> Arc<dyn crate::application::Application + Send + Sync> {
244 eprintln!(
245 "[proxy_config] ERROR: {reason} — this route answers 503 until the auth configuration is fixed."
246 );
247 arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(AuthMisconfigured))
248 };
249 app = match auth_cfg {
250 AuthConfig::Bearer { token_env } => {
251 let token = std::env::var(token_env).unwrap_or_default();
252 if token.is_empty() {
253 deny(format!("bearer auth: env var '{token_env}' is unset or empty"))
254 } else {
255 arc_app(
256 WithMiddleware::new(ArcApp(Arc::clone(&app)))
257 .wrap(BearerAuthMiddleware { token: Arc::new(token) }),
258 )
259 }
260 }
261 #[cfg(feature = "auth")]
262 AuthConfig::Jwt { secret_env } => {
263 let secret = std::env::var(secret_env).unwrap_or_default();
264 if secret.is_empty() {
265 deny(format!("JWT auth: env var '{secret_env}' is unset or empty"))
266 } else {
267 arc_app(
268 WithMiddleware::new(ArcApp(Arc::clone(&app)))
269 .wrap(crate::auth::JwtLayer::new(secret)),
270 )
271 }
272 }
273 #[cfg(not(feature = "auth"))]
274 AuthConfig::Jwt { .. } => {
275 deny("JWT auth requires the 'auth' feature, which this build doesn't include".to_string())
276 }
277 #[cfg(feature = "auth")]
278 AuthConfig::Basic { htpasswd_file } => {
279 match crate::auth::BasicAuthLayer::from_htpasswd_file(htpasswd_file) {
280 Ok(layer) => arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer)),
281 Err(e) => deny(format!("basic auth: {e}")),
282 }
283 }
284 #[cfg(not(feature = "auth"))]
285 AuthConfig::Basic { .. } => {
286 deny("basic auth requires the 'auth' feature, which this build doesn't include".to_string())
287 }
288 };
289 }
290
291 if let Some(ref rl_cfg) = mw.rate_limit {
293 let limiter = Arc::new(crate::rate_limit::RateLimiter::new(
294 rl_cfg.max_requests,
295 rl_cfg.window_secs,
296 ));
297 app = arc_app(
298 WithMiddleware::new(ArcApp(Arc::clone(&app)))
299 .wrap(PerRouteRateLimit(limiter)),
300 );
301 }
302
303 if !mw.ip_allow.is_empty() {
305 let filter = crate::ip_filter::IpFilter::allow(mw.ip_allow.iter().map(|s| s.as_str()));
306 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
307 } else if !mw.ip_deny.is_empty() {
308 let filter = crate::ip_filter::IpFilter::deny(mw.ip_deny.iter().map(|s| s.as_str()));
309 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
310 }
311
312 if let Some(timeout_ms) = mw.timeout_ms {
315 app = arc_app(crate::timeout::TimeoutLayer::from_arc(
316 app,
317 std::time::Duration::from_millis(timeout_ms),
318 ));
319 }
320
321 if let Some(max_body_size) = mw.max_body_size {
324 app = arc_app(
325 WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(PerRouteMaxBodySize(max_body_size)),
326 );
327 }
328
329 app
330}
331
332fn apply_request_rewrite_rule(
333 layer: crate::rewrite::RewriteLayer,
334 rule: &crate::proxy_config::RewriteRuleConfig,
335) -> crate::rewrite::RewriteLayer {
336 match rule.type_.as_str() {
337 "header_set" => {
338 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
339 return layer.request_header_set(name, value);
340 }
341 }
342 "header_remove" => {
343 if let Some(name) = &rule.name {
344 return layer.request_header_remove(name);
345 }
346 }
347 "uri_set" => {
348 if let Some(value) = &rule.value {
349 return layer.request_uri_set(value);
350 }
351 }
352 "uri_strip_prefix" | "strip_prefix" => {
353 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
354 return layer.request_uri_strip_prefix(prefix);
355 }
356 }
357 "uri_add_prefix" | "add_prefix" => {
358 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
359 return layer.request_uri_add_prefix(prefix);
360 }
361 }
362 _ => {}
363 }
364 layer
365}
366
367fn apply_response_rewrite_rule(
368 layer: crate::rewrite::RewriteLayer,
369 rule: &crate::proxy_config::RewriteRuleConfig,
370) -> crate::rewrite::RewriteLayer {
371 match rule.type_.as_str() {
372 "header_set" => {
373 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
374 return layer.response_header_set(name, value);
375 }
376 }
377 "header_remove" => {
378 if let Some(name) = &rule.name {
379 return layer.response_header_remove(name);
380 }
381 }
382 "status" => {
383 if let (Some(code), Some(reason)) = (&rule.code, &rule.reason) {
384 return layer.response_status(*code as i16, reason);
385 }
386 }
387 "body_replace" => {
388 if let (Some(from), Some(to)) = (&rule.from, &rule.to) {
389 return layer.response_body_replace(from, to);
390 }
391 }
392 _ => {}
393 }
394 layer
395}
396
397struct ArcApp(Arc<dyn crate::application::Application + Send + Sync>);
402
403impl crate::application::Application for ArcApp {
404 fn execute(
405 &self,
406 request: &crate::request::Request,
407 conn: &crate::server::ConnectionInfo,
408 ) -> Result<crate::response::Response, String> {
409 self.0.execute(request, conn)
410 }
411}
412
413impl Clone for ArcApp {
414 fn clone(&self) -> Self {
415 ArcApp(Arc::clone(&self.0))
416 }
417}