Skip to content

Commit 578fd0e

Browse files
committed
perf: Optimize reverse proxy performance and fix issues
- Reuse HTTP clients instead of creating new ones per request/health check - Fix hardcoded failure threshold to use configurable value - Add proper error handling for URL parsing (no more unwraps) - Fix X-Forwarded header parsing with safe error handling - Remove unused rand dependency - Suppress dead code warnings for future config fields
1 parent fe4a040 commit 578fd0e

File tree

3 files changed

+37
-88
lines changed

3 files changed

+37
-88
lines changed

Cargo.lock

Lines changed: 0 additions & 71 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ serde_json = "1.0"
1414
log = "0.4"
1515
env_logger = "0.10"
1616
anyhow = "1.0"
17-
rand = "0.8"
1817
chrono = { version = "0.4", features = ["serde"] }

src/main.rs

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use log::{info, warn, error, debug};
1313
use serde::Deserialize;
1414
use 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)]
1720
struct 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

7983
impl 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 {
191206
struct 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

196212
impl 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

Comments
 (0)