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 BearerAuthMiddleware, 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_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 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 arc_app(crate::proxy_config::NullApp)
106 }
107 };
108
109 let handler = apply_middleware(base_handler, &route.middleware);
111
112 compiled.push(CompiledRoute { matcher, handler });
113 }
114
115 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
179fn apply_middleware(
187 handler: Arc<dyn crate::application::Application + Send + Sync>,
188 mw: &MiddlewareConfig,
189) -> Arc<dyn crate::application::Application + Send + Sync> {
190 let mut app: Arc<dyn crate::application::Application + Send + Sync> = handler;
193
194 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 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 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 }
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 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 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
332struct 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}