@@ -13,6 +13,9 @@ use log::{info, warn, error, debug};
1313use serde:: Deserialize ;
1414use chrono:: { DateTime , Utc } ;
1515
16+ // Allow dead code for configuration fields that may be used in future features
17+ #[ allow( dead_code) ]
18+
1619#[ derive( Debug , Clone , Deserialize ) ]
1720struct Config {
1821 server : ServerConfig ,
@@ -74,6 +77,7 @@ struct LoadBalancer {
7477 backends : Vec < Backend > ,
7578 health_status : Arc < tokio:: sync:: RwLock < HashMap < String , BackendHealth > > > ,
7679 round_robin_counter : AtomicUsize ,
80+ http_client : hyper_util:: client:: legacy:: Client < hyper_util:: client:: legacy:: connect:: HttpConnector , Full < hyper:: body:: Bytes > > ,
7781}
7882
7983impl LoadBalancer {
@@ -93,10 +97,15 @@ impl LoadBalancer {
9397 }
9498 }
9599
100+ // Create reusable HTTP client
101+ let http_client = hyper_util:: client:: legacy:: Client :: builder ( hyper_util:: rt:: TokioExecutor :: new ( ) )
102+ . build_http :: < Full < hyper:: body:: Bytes > > ( ) ;
103+
96104 Self {
97105 backends,
98106 health_status,
99107 round_robin_counter : AtomicUsize :: new ( 0 ) ,
108+ http_client,
100109 }
101110 }
102111
@@ -120,11 +129,11 @@ impl LoadBalancer {
120129 Some ( healthy_backends[ index] . clone ( ) )
121130 }
122131
123- async fn mark_backend_unhealthy ( & self , url : & str ) {
132+ async fn mark_backend_unhealthy ( & self , url : & str , failure_threshold : u32 ) {
124133 let mut health = self . health_status . write ( ) . await ;
125134 if let Some ( backend_health) = health. get_mut ( url) {
126135 backend_health. failures += 1 ;
127- if backend_health. failures >= 3 {
136+ if backend_health. failures >= failure_threshold {
128137 backend_health. healthy = false ;
129138 warn ! ( "Backend {} marked as unhealthy after {} failures" , url, backend_health. failures) ;
130139 }
@@ -136,13 +145,19 @@ impl LoadBalancer {
136145 return ;
137146 }
138147
139- let client = hyper_util:: client:: legacy:: Client :: builder ( hyper_util:: rt:: TokioExecutor :: new ( ) )
140- . build_http :: < Full < hyper:: body:: Bytes > > ( ) ;
141-
142148 for backend in & self . backends {
143149 let health_url = format ! ( "{}{}" , backend. url, backend. health_check_path) ;
144150
145- match client. get ( health_url. parse ( ) . unwrap ( ) ) . await {
151+ // Parse URL safely
152+ let uri = match health_url. parse :: < Uri > ( ) {
153+ Ok ( uri) => uri,
154+ Err ( e) => {
155+ error ! ( "Invalid health check URL {}: {}" , health_url, e) ;
156+ continue ;
157+ }
158+ } ;
159+
160+ match self . http_client . get ( uri) . await {
146161 Ok ( response) => {
147162 let status = response. status ( ) ;
148163 let mut health = self . health_status . write ( ) . await ;
@@ -191,16 +206,22 @@ impl LoadBalancer {
191206struct ProxyService {
192207 load_balancer : Arc < LoadBalancer > ,
193208 config : Arc < Config > ,
209+ http_client : hyper_util:: client:: legacy:: Client < hyper_util:: client:: legacy:: connect:: HttpConnector , Full < hyper:: body:: Bytes > > ,
194210}
195211
196212impl ProxyService {
197213 fn new ( config : Config ) -> Self {
198214 let load_balancer = Arc :: new ( LoadBalancer :: new ( config. backends . clone ( ) ) ) ;
199215 let config = Arc :: new ( config) ;
200216
217+ // Create reusable HTTP client
218+ let http_client = hyper_util:: client:: legacy:: Client :: builder ( hyper_util:: rt:: TokioExecutor :: new ( ) )
219+ . build_http :: < Full < hyper:: body:: Bytes > > ( ) ;
220+
201221 Self {
202222 load_balancer,
203223 config,
224+ http_client,
204225 }
205226 }
206227
@@ -239,10 +260,6 @@ impl ProxyService {
239260 }
240261 } ;
241262
242- // Create the client
243- let client = hyper_util:: client:: legacy:: Client :: builder ( hyper_util:: rt:: TokioExecutor :: new ( ) )
244- . build_http :: < Full < hyper:: body:: Bytes > > ( ) ;
245-
246263 // Collect the request body
247264 let body_bytes = match req. collect ( ) . await {
248265 Ok ( collected) => collected. to_bytes ( ) ,
@@ -269,27 +286,31 @@ impl ProxyService {
269286 }
270287 }
271288
272- // Add X-Forwarded headers
273- proxy_req. headers_mut ( ) . insert ( "X-Forwarded-For" , remote_addr. parse ( ) . unwrap ( ) ) ;
274- proxy_req. headers_mut ( ) . insert ( "X-Forwarded-Proto" , "http" . parse ( ) . unwrap ( ) ) ;
289+ // Add X-Forwarded headers safely
290+ if let Ok ( forwarded_for) = remote_addr. parse ( ) {
291+ proxy_req. headers_mut ( ) . insert ( "X-Forwarded-For" , forwarded_for) ;
292+ }
293+ if let Ok ( forwarded_proto) = "http" . parse ( ) {
294+ proxy_req. headers_mut ( ) . insert ( "X-Forwarded-Proto" , forwarded_proto) ;
295+ }
275296
276297 // Send the request
277298 let response = match tokio:: time:: timeout (
278299 Duration :: from_secs ( self . config . timeouts . request_timeout_seconds ) ,
279- client . request ( proxy_req)
300+ self . http_client . request ( proxy_req)
280301 ) . await {
281302 Ok ( Ok ( response) ) => response,
282303 Ok ( Err ( e) ) => {
283304 error ! ( "Request to backend {} failed: {}" , backend. url, e) ;
284- self . load_balancer . mark_backend_unhealthy ( & backend. url ) . await ;
305+ self . load_balancer . mark_backend_unhealthy ( & backend. url , self . config . health_checks . failure_threshold ) . await ;
285306 return Ok ( Response :: builder ( )
286307 . status ( StatusCode :: BAD_GATEWAY )
287308 . body ( Full :: new ( hyper:: body:: Bytes :: from ( "Backend request failed" ) ) )
288309 . unwrap ( ) ) ;
289310 }
290311 Err ( _) => {
291312 error ! ( "Request to backend {} timed out" , backend. url) ;
292- self . load_balancer . mark_backend_unhealthy ( & backend. url ) . await ;
313+ self . load_balancer . mark_backend_unhealthy ( & backend. url , self . config . health_checks . failure_threshold ) . await ;
293314 return Ok ( Response :: builder ( )
294315 . status ( StatusCode :: GATEWAY_TIMEOUT )
295316 . body ( Full :: new ( hyper:: body:: Bytes :: from ( "Backend request timed out" ) ) )
0 commit comments