Skip to main content

rust_web_server/proxy_config/
builder.rs

1//! Builder: converts a `ProxyConfig` into a live `ConfigDrivenApp` with
2//! health checkers and L4/WS proxy threads.
3
4use 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    BearerAuthMiddleware, PerRouteRateLimit,
12};
13
14/// Build a `ConfigDrivenApp` from `rws.config.toml` and spawn L4/WS proxy
15/// threads. Returns the app and a list of background thread handles.
16pub fn build_from_file() -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
17    let config = ProxyConfig::load();
18    build(config)
19}
20
21/// Build a `ConfigDrivenApp` from a `ProxyConfig` struct.
22pub fn build(config: ProxyConfig) -> (ConfigDrivenApp, Vec<std::thread::JoinHandle<()>>) {
23    // ── 1. Build upstream name → live backends map ─────────────────────────
24    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    // ── 2. Build compiled routes ───────────────────────────────────────────
41    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        // Build the action handler (base application)
47        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                            // Fallback: upstream not declared, use name as single backend
61                            Arc::new(RwLock::new(vec![upstream.clone()]))
62                        });
63                    let upstream_tls = config.upstreams.iter()
64                        .find(|u| u.name == *upstream)
65                        .map(|u| u.tls)
66                        .unwrap_or(false);
67                    arc_app(DynamicProxy::new(
68                        live,
69                        *connect_timeout_ms,
70                        *read_timeout_ms,
71                        strip_path_prefix.clone(),
72                        add_path_prefix.clone(),
73                        upstream_tls,
74                    ))
75                }
76
77                ActionConfig::Redirect { location, status } => {
78                    arc_app(RedirectAdapter::new(location.clone(), *status))
79                }
80
81                ActionConfig::Respond { status, body, content_type } => {
82                    arc_app(RespondAdapter::new(*status, body.clone(), content_type.clone()))
83                }
84
85                ActionConfig::Static { root, index } => {
86                    arc_app(StaticAdapter::new(root.clone(), index.clone()))
87                }
88
89                // Grpc action — use DynamicProxy (it forwards over HTTP/1.1;
90                // a future version could plug in H2ReverseProxy here)
91                ActionConfig::Grpc { upstream, connect_timeout_ms, read_timeout_ms } => {
92                    let live = upstream_lives
93                        .get(upstream.as_str())
94                        .cloned()
95                        .unwrap_or_else(|| Arc::new(RwLock::new(vec![upstream.clone()])));
96                    let upstream_tls = config.upstreams.iter()
97                        .find(|u| u.name == *upstream)
98                        .map(|u| u.tls)
99                        .unwrap_or(false);
100                    arc_app(DynamicProxy::new(live, *connect_timeout_ms, *read_timeout_ms, None, None, upstream_tls))
101                }
102
103                ActionConfig::Mcp | ActionConfig::Unknown(_) => {
104                    // No-op: these fall through to fallback
105                    arc_app(crate::proxy_config::NullApp)
106                }
107            };
108
109        // Wrap the base handler with middleware layers
110        let handler = apply_middleware(base_handler, &route.middleware);
111
112        compiled.push(CompiledRoute { matcher, handler });
113    }
114
115    // ── 3. Spawn L4/WS proxy threads ──────────────────────────────────────
116    let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
117
118    for tcp_cfg in &config.tcp_proxies {
119        let listen = tcp_cfg.listen.clone();
120        let backends = tcp_cfg.backends.clone();
121        let timeout_ms = tcp_cfg.connect_timeout_ms;
122        let name = tcp_cfg.name.clone();
123        let h = std::thread::Builder::new()
124            .name(format!("tcp-proxy-{}", name))
125            .spawn(move || {
126                let proxy = crate::tcp_proxy::TcpProxy::new(backends)
127                    .connect_timeout_ms(timeout_ms);
128                if let Err(e) = proxy.bind(&listen) {
129                    eprintln!("[tcp_proxy:{}] {}", name, e);
130                }
131            })
132            .expect("failed to spawn tcp proxy thread");
133        handles.push(h);
134    }
135
136    for udp_cfg in &config.udp_proxies {
137        let listen = udp_cfg.listen.clone();
138        let backends = udp_cfg.backends.clone();
139        let reply_timeout_ms = udp_cfg.reply_timeout_ms;
140        let buffer_size = udp_cfg.buffer_size;
141        let name = udp_cfg.name.clone();
142        let h = std::thread::Builder::new()
143            .name(format!("udp-proxy-{}", name))
144            .spawn(move || {
145                let proxy = crate::udp_proxy::UdpProxy::new(backends)
146                    .reply_timeout_ms(reply_timeout_ms)
147                    .buffer_size(buffer_size);
148                if let Err(e) = proxy.bind(&listen) {
149                    eprintln!("[udp_proxy:{}] {}", name, e);
150                }
151            })
152            .expect("failed to spawn udp proxy thread");
153        handles.push(h);
154    }
155
156    for ws_cfg in &config.ws_proxies {
157        let listen = ws_cfg.listen.clone();
158        let backends = ws_cfg.backends.clone();
159        let connect_timeout_ms = ws_cfg.connect_timeout_ms;
160        let read_timeout_ms = ws_cfg.read_timeout_ms;
161        let name = ws_cfg.name.clone();
162        let h = std::thread::Builder::new()
163            .name(format!("ws-proxy-{}", name))
164            .spawn(move || {
165                let proxy = crate::ws_proxy::WsProxy::new(backends)
166                    .connect_timeout_ms(connect_timeout_ms)
167                    .read_timeout_ms(read_timeout_ms);
168                if let Err(e) = proxy.bind(&listen) {
169                    eprintln!("[ws_proxy:{}] {}", name, e);
170                }
171            })
172            .expect("failed to spawn ws proxy thread");
173        handles.push(h);
174    }
175
176    (ConfigDrivenApp::new(compiled), handles)
177}
178
179/// Wrap `handler` with the middleware layers described by `mw`.
180/// Layers are applied innermost → outermost in the order:
181///   1. IP filter (outermost — rejects early)
182///   2. Rate limit
183///   3. Auth
184///   4. Rewrite (request + response rules combined)
185///   5. Cache (innermost middleware — closest to handler)
186fn apply_middleware(
187    handler: Arc<dyn crate::application::Application + Send + Sync>,
188    mw: &MiddlewareConfig,
189) -> Arc<dyn crate::application::Application + Send + Sync> {
190    // Wrap the Arc<dyn Application> in an ArcApp adapter so it implements
191    // Application itself (needed for WithMiddleware::new).
192    let mut app: Arc<dyn crate::application::Application + Send + Sync> = handler;
193
194    // ── Cache (applied first so it's the innermost middleware) ─────────────
195    if let Some(ref cache_cfg) = mw.cache {
196        let mut layer = crate::cache::CacheLayer::memory(1000).ttl(cache_cfg.ttl_secs);
197        for vh in &cache_cfg.vary_by {
198            layer = layer.vary_by_header(vh.as_str());
199        }
200        app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
201    }
202
203    // ── Rewrite ────────────────────────────────────────────────────────────
204    if !mw.rewrite_request.is_empty() || !mw.rewrite_response.is_empty() {
205        let mut layer = crate::rewrite::RewriteLayer::new();
206        for rule in &mw.rewrite_request {
207            layer = apply_request_rewrite_rule(layer, rule);
208        }
209        for rule in &mw.rewrite_response {
210            layer = apply_response_rewrite_rule(layer, rule);
211        }
212        app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
213    }
214
215    // ── Auth ───────────────────────────────────────────────────────────────
216    if let Some(ref auth_cfg) = mw.auth {
217        match auth_cfg {
218            AuthConfig::Bearer { token_env } => {
219                let token = std::env::var(token_env).unwrap_or_default();
220                if !token.is_empty() {
221                    app = arc_app(
222                        WithMiddleware::new(ArcApp(Arc::clone(&app)))
223                            .wrap(BearerAuthMiddleware { token: Arc::new(token) }),
224                    );
225                }
226            }
227            #[cfg(feature = "auth")]
228            AuthConfig::Jwt { secret_env } => {
229                let _secret = std::env::var(secret_env).unwrap_or_default();
230                // JWT verification would be wired in here when auth feature is enabled.
231                // For now this is a no-op placeholder.
232            }
233            #[cfg(not(feature = "auth"))]
234            AuthConfig::Jwt { .. } => {
235                eprintln!("[proxy_config] JWT auth requires the 'auth' feature; skipping.");
236            }
237            AuthConfig::Basic { .. } => {
238                eprintln!("[proxy_config] Basic auth is not yet implemented; skipping.");
239            }
240        }
241    }
242
243    // ── Rate limit ─────────────────────────────────────────────────────────
244    if let Some(ref rl_cfg) = mw.rate_limit {
245        let limiter = Arc::new(crate::rate_limit::RateLimiter::new(
246            rl_cfg.max_requests,
247            rl_cfg.window_secs,
248        ));
249        app = arc_app(
250            WithMiddleware::new(ArcApp(Arc::clone(&app)))
251                .wrap(PerRouteRateLimit(limiter)),
252        );
253    }
254
255    // ── IP filter (outermost) ──────────────────────────────────────────────
256    if !mw.ip_allow.is_empty() {
257        let filter = crate::ip_filter::IpFilter::allow(mw.ip_allow.iter().map(|s| s.as_str()));
258        app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
259    } else if !mw.ip_deny.is_empty() {
260        let filter = crate::ip_filter::IpFilter::deny(mw.ip_deny.iter().map(|s| s.as_str()));
261        app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
262    }
263
264    app
265}
266
267fn apply_request_rewrite_rule(
268    layer: crate::rewrite::RewriteLayer,
269    rule: &crate::proxy_config::RewriteRuleConfig,
270) -> crate::rewrite::RewriteLayer {
271    match rule.type_.as_str() {
272        "header_set" => {
273            if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
274                return layer.request_header_set(name, value);
275            }
276        }
277        "header_remove" => {
278            if let Some(name) = &rule.name {
279                return layer.request_header_remove(name);
280            }
281        }
282        "uri_set" => {
283            if let Some(value) = &rule.value {
284                return layer.request_uri_set(value);
285            }
286        }
287        "uri_strip_prefix" | "strip_prefix" => {
288            if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
289                return layer.request_uri_strip_prefix(prefix);
290            }
291        }
292        "uri_add_prefix" | "add_prefix" => {
293            if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
294                return layer.request_uri_add_prefix(prefix);
295            }
296        }
297        _ => {}
298    }
299    layer
300}
301
302fn apply_response_rewrite_rule(
303    layer: crate::rewrite::RewriteLayer,
304    rule: &crate::proxy_config::RewriteRuleConfig,
305) -> crate::rewrite::RewriteLayer {
306    match rule.type_.as_str() {
307        "header_set" => {
308            if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
309                return layer.response_header_set(name, value);
310            }
311        }
312        "header_remove" => {
313            if let Some(name) = &rule.name {
314                return layer.response_header_remove(name);
315            }
316        }
317        "status" => {
318            if let (Some(code), Some(reason)) = (&rule.code, &rule.reason) {
319                return layer.response_status(*code as i16, reason);
320            }
321        }
322        "body_replace" => {
323            if let (Some(from), Some(to)) = (&rule.from, &rule.to) {
324                return layer.response_body_replace(from, to);
325            }
326        }
327        _ => {}
328    }
329    layer
330}
331
332// ── ArcApp adapter ─────────────────────────────────────────────────────────────
333
334/// Wraps `Arc<dyn Application + Send + Sync>` to implement `Application`
335/// (needed because you can't implement a foreign trait on a foreign type).
336struct ArcApp(Arc<dyn crate::application::Application + Send + Sync>);
337
338impl crate::application::Application for ArcApp {
339    fn execute(
340        &self,
341        request: &crate::request::Request,
342        conn: &crate::server::ConnectionInfo,
343    ) -> Result<crate::response::Response, String> {
344        self.0.execute(request, conn)
345    }
346}
347
348impl Clone for ArcApp {
349    fn clone(&self) -> Self {
350        ArcApp(Arc::clone(&self.0))
351    }
352}