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, arc_app, BearerAuthMiddleware,
11 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 arc_app(DynamicProxy::new(
64 live,
65 *connect_timeout_ms,
66 *read_timeout_ms,
67 strip_path_prefix.clone(),
68 add_path_prefix.clone(),
69 ))
70 }
71
72 ActionConfig::Redirect { location, status } => {
73 arc_app(RedirectAdapter::new(location.clone(), *status))
74 }
75
76 ActionConfig::Respond { status, body, content_type } => {
77 arc_app(RespondAdapter::new(*status, body.clone(), content_type.clone()))
78 }
79
80 ActionConfig::Static { .. } => {
82 use crate::core::New;
83 arc_app(crate::app::App::new())
84 }
85
86 ActionConfig::Grpc { upstream, connect_timeout_ms, read_timeout_ms } => {
89 let live = upstream_lives
90 .get(upstream.as_str())
91 .cloned()
92 .unwrap_or_else(|| Arc::new(RwLock::new(vec![upstream.clone()])));
93 arc_app(DynamicProxy::new(live, *connect_timeout_ms, *read_timeout_ms, None, None))
94 }
95
96 ActionConfig::Mcp | ActionConfig::Unknown(_) => {
97 arc_app(crate::proxy_config::NullApp)
99 }
100 };
101
102 let handler = apply_middleware(base_handler, &route.middleware);
104
105 compiled.push(CompiledRoute { matcher, handler });
106 }
107
108 let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
110
111 for tcp_cfg in &config.tcp_proxies {
112 let listen = tcp_cfg.listen.clone();
113 let backends = tcp_cfg.backends.clone();
114 let timeout_ms = tcp_cfg.connect_timeout_ms;
115 let name = tcp_cfg.name.clone();
116 let h = std::thread::Builder::new()
117 .name(format!("tcp-proxy-{}", name))
118 .spawn(move || {
119 let proxy = crate::tcp_proxy::TcpProxy::new(backends)
120 .connect_timeout_ms(timeout_ms);
121 if let Err(e) = proxy.bind(&listen) {
122 eprintln!("[tcp_proxy:{}] {}", name, e);
123 }
124 })
125 .expect("failed to spawn tcp proxy thread");
126 handles.push(h);
127 }
128
129 for udp_cfg in &config.udp_proxies {
130 let listen = udp_cfg.listen.clone();
131 let backends = udp_cfg.backends.clone();
132 let reply_timeout_ms = udp_cfg.reply_timeout_ms;
133 let buffer_size = udp_cfg.buffer_size;
134 let name = udp_cfg.name.clone();
135 let h = std::thread::Builder::new()
136 .name(format!("udp-proxy-{}", name))
137 .spawn(move || {
138 let proxy = crate::udp_proxy::UdpProxy::new(backends)
139 .reply_timeout_ms(reply_timeout_ms)
140 .buffer_size(buffer_size);
141 if let Err(e) = proxy.bind(&listen) {
142 eprintln!("[udp_proxy:{}] {}", name, e);
143 }
144 })
145 .expect("failed to spawn udp proxy thread");
146 handles.push(h);
147 }
148
149 for ws_cfg in &config.ws_proxies {
150 let listen = ws_cfg.listen.clone();
151 let backends = ws_cfg.backends.clone();
152 let connect_timeout_ms = ws_cfg.connect_timeout_ms;
153 let read_timeout_ms = ws_cfg.read_timeout_ms;
154 let name = ws_cfg.name.clone();
155 let h = std::thread::Builder::new()
156 .name(format!("ws-proxy-{}", name))
157 .spawn(move || {
158 let proxy = crate::ws_proxy::WsProxy::new(backends)
159 .connect_timeout_ms(connect_timeout_ms)
160 .read_timeout_ms(read_timeout_ms);
161 if let Err(e) = proxy.bind(&listen) {
162 eprintln!("[ws_proxy:{}] {}", name, e);
163 }
164 })
165 .expect("failed to spawn ws proxy thread");
166 handles.push(h);
167 }
168
169 (ConfigDrivenApp::new(compiled), handles)
170}
171
172fn apply_middleware(
180 handler: Arc<dyn crate::application::Application + Send + Sync>,
181 mw: &MiddlewareConfig,
182) -> Arc<dyn crate::application::Application + Send + Sync> {
183 let mut app: Arc<dyn crate::application::Application + Send + Sync> = handler;
186
187 if let Some(ref cache_cfg) = mw.cache {
189 let mut layer = crate::cache::CacheLayer::memory(1000).ttl(cache_cfg.ttl_secs);
190 for vh in &cache_cfg.vary_by {
191 layer = layer.vary_by_header(vh.as_str());
192 }
193 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
194 }
195
196 if !mw.rewrite_request.is_empty() || !mw.rewrite_response.is_empty() {
198 let mut layer = crate::rewrite::RewriteLayer::new();
199 for rule in &mw.rewrite_request {
200 layer = apply_request_rewrite_rule(layer, rule);
201 }
202 for rule in &mw.rewrite_response {
203 layer = apply_response_rewrite_rule(layer, rule);
204 }
205 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(layer));
206 }
207
208 if let Some(ref auth_cfg) = mw.auth {
210 match auth_cfg {
211 AuthConfig::Bearer { token_env } => {
212 let token = std::env::var(token_env).unwrap_or_default();
213 if !token.is_empty() {
214 app = arc_app(
215 WithMiddleware::new(ArcApp(Arc::clone(&app)))
216 .wrap(BearerAuthMiddleware { token: Arc::new(token) }),
217 );
218 }
219 }
220 #[cfg(feature = "auth")]
221 AuthConfig::Jwt { secret_env } => {
222 let _secret = std::env::var(secret_env).unwrap_or_default();
223 }
226 #[cfg(not(feature = "auth"))]
227 AuthConfig::Jwt { .. } => {
228 eprintln!("[proxy_config] JWT auth requires the 'auth' feature; skipping.");
229 }
230 AuthConfig::Basic { .. } => {
231 eprintln!("[proxy_config] Basic auth is not yet implemented; skipping.");
232 }
233 }
234 }
235
236 if let Some(ref rl_cfg) = mw.rate_limit {
238 let limiter = Arc::new(crate::rate_limit::RateLimiter::new(
239 rl_cfg.max_requests,
240 rl_cfg.window_secs,
241 ));
242 app = arc_app(
243 WithMiddleware::new(ArcApp(Arc::clone(&app)))
244 .wrap(PerRouteRateLimit(limiter)),
245 );
246 }
247
248 if !mw.ip_allow.is_empty() {
250 let filter = crate::ip_filter::IpFilter::allow(mw.ip_allow.iter().map(|s| s.as_str()));
251 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
252 } else if !mw.ip_deny.is_empty() {
253 let filter = crate::ip_filter::IpFilter::deny(mw.ip_deny.iter().map(|s| s.as_str()));
254 app = arc_app(WithMiddleware::new(ArcApp(Arc::clone(&app))).wrap(filter));
255 }
256
257 app
258}
259
260fn apply_request_rewrite_rule(
261 layer: crate::rewrite::RewriteLayer,
262 rule: &crate::proxy_config::RewriteRuleConfig,
263) -> crate::rewrite::RewriteLayer {
264 match rule.type_.as_str() {
265 "header_set" => {
266 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
267 return layer.request_header_set(name, value);
268 }
269 }
270 "header_remove" => {
271 if let Some(name) = &rule.name {
272 return layer.request_header_remove(name);
273 }
274 }
275 "uri_set" => {
276 if let Some(value) = &rule.value {
277 return layer.request_uri_set(value);
278 }
279 }
280 "uri_strip_prefix" | "strip_prefix" => {
281 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
282 return layer.request_uri_strip_prefix(prefix);
283 }
284 }
285 "uri_add_prefix" | "add_prefix" => {
286 if let Some(prefix) = rule.prefix.as_ref().or(rule.value.as_ref()) {
287 return layer.request_uri_add_prefix(prefix);
288 }
289 }
290 _ => {}
291 }
292 layer
293}
294
295fn apply_response_rewrite_rule(
296 layer: crate::rewrite::RewriteLayer,
297 rule: &crate::proxy_config::RewriteRuleConfig,
298) -> crate::rewrite::RewriteLayer {
299 match rule.type_.as_str() {
300 "header_set" => {
301 if let (Some(name), Some(value)) = (&rule.name, &rule.value) {
302 return layer.response_header_set(name, value);
303 }
304 }
305 "header_remove" => {
306 if let Some(name) = &rule.name {
307 return layer.response_header_remove(name);
308 }
309 }
310 "status" => {
311 if let (Some(code), Some(reason)) = (&rule.code, &rule.reason) {
312 return layer.response_status(*code as i16, reason);
313 }
314 }
315 "body_replace" => {
316 if let (Some(from), Some(to)) = (&rule.from, &rule.to) {
317 return layer.response_body_replace(from, to);
318 }
319 }
320 _ => {}
321 }
322 layer
323}
324
325struct ArcApp(Arc<dyn crate::application::Application + Send + Sync>);
330
331impl crate::application::Application for ArcApp {
332 fn execute(
333 &self,
334 request: &crate::request::Request,
335 conn: &crate::server::ConnectionInfo,
336 ) -> Result<crate::response::Response, String> {
337 self.0.execute(request, conn)
338 }
339}
340
341impl Clone for ArcApp {
342 fn clone(&self) -> Self {
343 ArcApp(Arc::clone(&self.0))
344 }
345}