1#[cfg(test)]
22mod tests;
23mod health;
24
25use std::borrow::Cow;
26use std::sync::Arc;
27use std::net::SocketAddr;
28use std::convert::Infallible;
29use tokio::sync::RwLock;
30use hyper::body::Incoming;
31use hyper::{Request, Response};
32use hyper_util::server::conn::auto::Builder as AutoBuilder;
33use hyper_util::rt::TokioExecutor;
34use hyper::service::service_fn;
35use hyper_util::rt::TokioIo;
36use bytes::Bytes;
37use futures_util::TryStreamExt;
38use http_body_util::{BodyExt, Full};
39use reqwest::Body;
40use serde::{Serialize, Deserialize};
41use crate::{error_fmt, warn_fmt, info_fmt, debug_fmt, trace_fmt};
42use tokio::signal;
43use crate::logging::middleware::LoggingMiddleware;
44use crate::logging::config::LoggingConfig;
45use std::time::Instant;use tokio::task::{Id, JoinSet};
46use crate::core::{ProxyCore, ProxyRequest, ProxyResponse, ProxyError, HttpMethod, RequestContext};
47use std::collections::HashMap;
48use tokio::sync::oneshot;
49use health::HealthServer;
50
51#[cfg(unix)]
52use tokio::signal::unix::{signal, SignalKind};
53
54#[cfg(feature = "opentelemetry")]
55use opentelemetry::{
56 global,
57 trace::{TraceContextExt, Tracer},
58 KeyValue,
59 Context,
60 trace::{Span, SpanBuilder, SpanKind, Status, SpanRef}
61};
62#[cfg(feature = "opentelemetry")]
63use opentelemetry_http::HeaderExtractor;
64#[cfg(feature = "opentelemetry")]
65use opentelemetry_semantic_conventions::attribute::{HTTP_FLAVOR, HTTP_HOST, HTTP_METHOD, HTTP_REQUEST_CONTENT_LENGTH, HTTP_RESPONSE_STATUS_CODE, HTTP_SCHEME, HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT, NET_PEER_IP};
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ServerConfig {
70 #[serde(default = "default_host")]
72 pub host: String,
73
74 #[serde(default = "default_port")]
76 pub port: u16,
77
78 #[serde(default = "default_health_port")]
80 pub health_port: u16,
81}
82
83fn default_host() -> String {
84 "127.0.0.1".to_string()
85}
86
87fn default_port() -> u16 {
88 8080
89}
90
91fn default_health_port() -> u16 {
92 8081
93}
94
95impl Default for ServerConfig {
96 fn default() -> Self {
97 Self {
98 host: default_host(),
99 port: default_port(),
100 health_port: default_health_port(),
101 }
102 }
103}
104
105#[derive(Debug, Clone)]
107pub struct ProxyServer {
108 config: ServerConfig,
110 core: Arc<ProxyCore>,
112 shutdown_senders: Arc<RwLock<HashMap<Id, oneshot::Sender<()>>>>,
114 logging_middleware: LoggingMiddleware,
116}
117
118impl ProxyServer {
119 pub fn new(config: ServerConfig, core: Arc<ProxyCore>) -> Self {
121 let logging_config = match core.config.get::<LoggingConfig>("proxy.logging") {
123 Ok(Some(config)) => config,
124 _ => LoggingConfig::default(),
125 };
126
127 let logging_middleware = LoggingMiddleware::new(logging_config);
129
130 Self {
131 config,
132 core,
133 shutdown_senders: Arc::new(RwLock::new(HashMap::new())),
134 logging_middleware,
135 }
136 }
137
138 pub async fn start(&self) -> Result<(), ProxyError> {
140 let addr = format!("{}:{}", self.config.host, self.config.port)
141 .parse::<SocketAddr>()
142 .map_err(|e| ProxyError::Other(format!("Invalid server address: {}", e)))?;
143
144 let listener = tokio::net::TcpListener::bind(addr)
145 .await
146 .map_err(|e| ProxyError::Other(format!("Failed to bind: {}", e)))?;
147
148 info_fmt!("Server", "Foxy proxy listening on http://{}", addr);
149
150 let health_server = HealthServer::new(self.config.health_port);
151 health_server.set_ready();
152
153 let ctrl_c = signal::ctrl_c();
155
156 #[cfg(unix)]
158 let mut term_stream = signal(SignalKind::terminate())
159 .map_err(|e| ProxyError::Other(format!("Cannot install SIGTERM handler: {}", e)))?;
160
161 #[cfg(unix)]
163 let sigterm = term_stream.recv();
164 #[cfg(not(unix))]
165 let sigterm = std::future::pending();
166
167 tokio::pin!(ctrl_c);
169 tokio::pin!(sigterm);
170
171 let shutdown_senders = self.shutdown_senders.clone();
173
174 let mut join_set = JoinSet::new();
176 let core = self.core.clone();
177
178 let shutdown_initiated = Arc::new(std::sync::atomic::AtomicBool::new(false));
180 let shutdown_initiated_clone = shutdown_initiated.clone();
181
182 loop {
183 tokio::select! {
184 _ = &mut ctrl_c => {
185 info_fmt!("Server", "Received Ctrl-C; initiating graceful shutdown");
186 shutdown_initiated_clone.store(true, std::sync::atomic::Ordering::SeqCst);
187 break;
188 }
189 _ = &mut sigterm => {
190 info_fmt!("Server", "Received SIGTERM; initiating graceful shutdown");
191 shutdown_initiated_clone.store(true, std::sync::atomic::Ordering::SeqCst);
192 break;
193 }
194 accept = listener.accept() => {
195 match accept {
196 Ok((stream, remote_addr)) => {
197 if shutdown_initiated.load(std::sync::atomic::Ordering::SeqCst) {
199 info_fmt!("Server", "Rejecting new connection during shutdown");
200 continue;
201 }
202
203 let core = core.clone();
204 let client_ip = remote_addr.ip().to_string();
205 let logging_middleware = self.logging_middleware.clone();
206 let (tx, rx) = oneshot::channel();
207 let shutdown_senders_clone = shutdown_senders.clone();
208
209 let handle = join_set.spawn(async move {
210 let task_id = tokio::task::id();
211
212 let service = service_fn(move |req: Request<Incoming>| {
213 debug_fmt!("Server", "Incoming over {:?}", &req.version());
214 handle_request(req, core.clone(), client_ip.clone(), logging_middleware.clone())
215 });
216 let io = TokioIo::new(stream);
217
218 let builder = {
219 let mut b = AutoBuilder::new(TokioExecutor::new());
220 b.http1();
221 b.http2();
222 b
223 };
224
225 let connection = builder.serve_connection(io, service);
227
228 let mut conn = std::pin::pin!(connection);
230
231 tokio::select! {
233 res = &mut conn => {
234 match res {
235 Ok(()) => debug_fmt!("Server", "Connection closed normally"),
236 Err(e) => {
237 let err_str = e.to_string();
239 if !err_str.contains("connection closed") &&
240 !err_str.contains("connection reset") {
241 error_fmt!("Server", "Connection error: {}", e);
242 }
243 }
244 }
245 }
246 _ = rx => {
247 debug_fmt!("Server", "Connection received shutdown signal, waiting for graceful close");
248 conn.as_mut().graceful_shutdown();
249
250 match conn.await {
252 Ok(()) => debug_fmt!("Server", "Connection closed gracefully after shutdown signal"),
253 Err(e) => {
254 let err_str = e.to_string();
255 if !err_str.contains("connection closed") &&
256 !err_str.contains("connection reset") {
257 error_fmt!("Server", "Connection error during graceful shutdown: {}", e);
258 }
259 }
260 }
261 }
262 }
263
264 shutdown_senders_clone.write().await.remove(&task_id);
266 debug_fmt!("Server", "Connection task {:?} completed", task_id);
267 });
268
269 shutdown_senders.write().await.insert(handle.id(), tx);
271 }
272 Err(e) => error_fmt!("Server", "Accept error: {}", e),
273 }
274 }
275 }
276 }
277
278 info_fmt!("Server", "Shutting down; waiting for {} connection(s)", join_set.len());
280
281 {
283 let mut senders = shutdown_senders.write().await;
284 info_fmt!("Server", "Signaling {} connections to shut down", senders.len());
285 for (task_id, sender) in senders.drain() {
286 debug_fmt!("Server", "Sending shutdown signal to task {:?}", task_id);
287 let _ = sender.send(());
288 }
289 }
290
291 let shutdown_timeout = tokio::time::Duration::from_secs(30);
293 let start_time = tokio::time::Instant::now();
294
295 let shutdown_future = async {
296 let mut completed = 0;
297 let total = join_set.len();
298
299 while let Some(res) = join_set.join_next().await {
300 completed += 1;
301 match res {
302 Ok(_) => debug_fmt!("Server", "Connection task completed ({}/{})", completed, total),
303 Err(e) if e.is_cancelled() => debug_fmt!("Server", "Connection task cancelled ({}/{})", completed, total),
304 Err(e) => error_fmt!("Server", "Connection task failed ({}/{}): {}", completed, total, e),
305 }
306
307 let elapsed = start_time.elapsed();
308 if completed % 10 == 0 || total - completed < 10 {
309 info_fmt!("Server", "Shutdown progress: {}/{} connections closed (elapsed: {:.1}s)",
310 completed, total, elapsed.as_secs_f32());
311 }
312 }
313 };
314
315 match tokio::time::timeout(shutdown_timeout, shutdown_future).await {
316 Ok(_) => {
317 let elapsed = start_time.elapsed();
318 info_fmt!("Server", "All connections drained gracefully in {:.1}s", elapsed.as_secs_f32());
319 }
320 Err(_) => {
321 warn_fmt!("Server", "Shutdown timed out after {} seconds, some connections may be forcefully closed",
322 shutdown_timeout.as_secs());
323 join_set.shutdown().await;
325 }
326 }
327
328 drop(health_server);
330
331 info_fmt!("Server", "Shutdown complete");
332 Ok(())
333 }
334}
335
336async fn convert_hyper_request(
338 req: Request<Incoming>,
339 client_ip: String,
340) -> Result<ProxyRequest, ProxyError> {
341
342 let method = HttpMethod::from(req.method());
343 let uri = req.uri().clone();
344 let path = uri.path().to_owned();
345 let query = uri.query().map(|q| q.to_owned());
346 let headers = req.headers().clone();
347
348 trace_fmt!("Server", "Converting request: {} {} with {} headers",
349 method, path, headers.len());
350
351 let hyper_stream = req.into_body().into_data_stream();
353 let byte_stream = hyper_stream.map_ok(Bytes::from);
354 let body = reqwest::Body::wrap_stream(byte_stream);
355
356 Ok(ProxyRequest {
357 method,
358 path,
359 query,
360 headers,
361 body,
362 context: Arc::new(RwLock::new(RequestContext {
363 client_ip: Some(client_ip),
364 start_time: Some(std::time::Instant::now()),
365 attributes: std::collections::HashMap::new(),
366 })),
367 custom_target: None,
368 })
369}
370
371fn convert_proxy_response(resp: ProxyResponse) -> Result<Response<Body>, ProxyError> {
373 trace_fmt!("Server", "Converting response with status {} and {} headers",
374 resp.status, resp.headers.len());
375
376 let stream = resp
377 .body
378 .into_data_stream()
379 .map_err(|e| {
380 error_fmt!("Server", "Error streaming response body: {}", e);
381 std::io::Error::new(std::io::ErrorKind::Other, e)
382 });
383
384 let body = Body::wrap_stream(stream);
385
386 let mut builder = Response::builder().status(resp.status);
387 let mut_headers = builder.headers_mut().ok_or_else(|| {
388 error_fmt!("Server", "Failed to get mutable headers from response builder");
389 ProxyError::Other("Failed to build response: unable to get mutable headers".into())
390 })?;
391 *mut_headers = resp.headers;
392
393 builder
394 .body(body)
395 .map_err(|e| {
396 let err = ProxyError::Other(e.to_string());
397 error_fmt!("Server", "Failed to build response: {}", err);
398 err
399 })
400}
401
402async fn handle_request(
404 req: Request<Incoming>,
405 core: Arc<ProxyCore>,
406 client_ip: String,
407 logging_middleware: LoggingMiddleware,
408) -> Result<Response<Body>, Infallible> {
409 let remote_addr = req.extensions().get::<SocketAddr>().cloned();
411 let (req, request_info) = logging_middleware.process(req, remote_addr).await;
412
413 let upstream_start = Instant::now();
415 #[cfg(feature = "opentelemetry")]
417 let span_context = {
418 let method = req.method().as_str().to_owned();
419 let path = req.uri().path().to_owned();
420 let full_url = req.uri().clone().to_string();
421 let scheme = req.uri().scheme_str().unwrap_or("http").to_owned();
422 let host = req.headers().get("host").and_then(|v| v.to_str().ok()).unwrap_or("-").to_owned();
423 let http_version = match req.version() { hyper::Version::HTTP_10 => "1.0", hyper::Version::HTTP_11 => "1.1", hyper::Version::HTTP_2 => "2", hyper::Version::HTTP_3 => "3", _ => "unknown" };
424 let req_content_len = req.headers().get("content-length").and_then(|v| v.to_str().ok()).and_then(|s| s.parse::<i64>().ok()).unwrap_or(0);
425 let user_agent = req.headers().get("user-agent").and_then(|v| v.to_str().ok()).unwrap_or("-").to_owned();
426 let peer_ip = client_ip.as_str().to_owned();
427
428 let context = extract_context_from_request(&req);
429 let mut span = global::tracer("foxy::proxy")
430 .build_with_context(SpanBuilder {
431 name: Cow::from(format!("{method} {path}")),
432 span_kind: Some(SpanKind::Server),
433 ..Default::default()
434 }, &context);
435
436 span.set_attributes([
437 KeyValue::new(HTTP_METHOD, method),
438 KeyValue::new(HTTP_URL, full_url.clone()),
439 KeyValue::new(HTTP_SCHEME, scheme),
440 KeyValue::new(HTTP_HOST, host),
441 KeyValue::new(HTTP_FLAVOR, http_version),
442 KeyValue::new(HTTP_REQUEST_CONTENT_LENGTH, req_content_len),
443 KeyValue::new(HTTP_USER_AGENT, user_agent),
444 KeyValue::new(NET_PEER_IP, peer_ip),
445 ]);
446
447 context.with_span(span)
448 };
449
450 let method = req.method().clone();
452 let path = req.uri().path().to_owned();
453
454 debug_fmt!("Server", "Received request: {} {}", method, path);
455
456 let proxy_req = match convert_hyper_request(req, client_ip.clone()).await {
457 Ok(r) => r,
458 Err(e) => {
459 error_fmt!("Server", "Failed to convert request {} {}: {}", method, path, e);
460 return Ok(Response::builder()
461 .status(500)
462 .body(Body::from("Internal Server Error"))
463 .unwrap());
464 }
465 };
466
467 #[cfg(feature = "opentelemetry")]
469 let span_clone = span_context.clone();
470
471 #[cfg(feature = "opentelemetry")]
472 let span_ref = span_context.span();
473
474 #[cfg(feature = "opentelemetry")]
475 let result = core.process_request(proxy_req, Some(span_clone)).await;
476
477 #[cfg(not(feature = "opentelemetry"))]
478 let result = core.process_request(proxy_req).await;
479
480 #[cfg(feature = "opentelemetry")]
482 {
483 match result.as_ref() {
484 Ok(r) => {
485 span_ref.set_status(Status::Ok)
486 },
487 Err(e) => {
488 span_ref.record_error(e);
489 span_ref.set_status(Status::Error { description: Cow::from(e.to_string()) })
490 }
491 }
492 }
493
494 let response: Result<Response<Body>, Infallible> = match result {
496 Ok(proxy_resp) => {
497 debug_fmt!("Server",
498 "Successfully processed request {} {} -> {}",
499 method,
500 path,
501 proxy_resp.status
502 );
503 match convert_proxy_response(proxy_resp) {
504 Ok(resp) => {
505 let upstream_duration = upstream_start.elapsed();
507
508 logging_middleware.log_response(&resp, &request_info, Some(upstream_duration));
510
511 Ok(resp)
512 },
513 Err(e) => {
514 error_fmt!("Server",
515 "Failed to convert response for {} {}: {}",
516 method,
517 path,
518 e
519 );
520 Ok(Response::builder()
521 .status(500)
522 .body(Body::from("Internal Server Error"))
523 .unwrap())
524 }
525 }
526 }
527 Err(e) => {
528 let (status, msg) = match &e {
529 ProxyError::Timeout(d) => {
530 warn_fmt!("Server", "Request {} {} timed out after {:?}", method, path, d);
531 (504, format!("Gateway Timeout after {d:?}"))
532 }
533 ProxyError::RoutingError(msg) => {
534 warn_fmt!("Server", "Routing error for {} {}: {}", method, path, msg);
535 (404, "Route not found".into())
536 }
537 ProxyError::SecurityError(msg) => {
538 warn_fmt!("Server", "Security error for {} {}: {}", method, path, msg);
539 (403, "Forbidden".into())
540 }
541 ProxyError::ClientError(err) => {
542 error_fmt!("Server", "Client error for {} {}: {}", method, path, err);
543 (502, "Bad Gateway".into())
544 }
545 _ => {
546 error_fmt!("Server", "Internal error processing {} {}: {}", method, path, e);
547 (500, "Internal Server Error".into())
548 }
549 };
550
551 Ok(Response::builder()
552 .status(status)
553 .body(Body::from(msg))
554 .unwrap())
555 }
556 };
557
558 if let Ok(resp) = &response {
560 if resp.status().is_client_error() || resp.status().is_server_error() {
561 logging_middleware.log_response(resp, &request_info, None);
562 }
563 }
564
565 #[cfg(feature = "opentelemetry")]
566 {
567 let status_code = response.as_ref().unwrap().status().as_u16();
568
569 span_ref.set_attribute(KeyValue::new(
570 HTTP_RESPONSE_STATUS_CODE,
571 status_code as i64,
572 ));
573 span_ref.end();
574 }
575
576 response
577}
578
579#[cfg(feature = "opentelemetry")]
581fn extract_context_from_request(req: &Request<Incoming>) -> Context {
582 global::get_text_map_propagator(|propagator| {
583 propagator.extract(&HeaderExtractor(req.headers()))
584 })
585}