Skip to main content

foxy/server/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! HTTP server implementation for Foxy.
6//!
7//! The server is a *thin* wrapper around **hyper-util**.  It owns the
8//! listening socket(s) and translates between Hyper's body types and the
9//! internal [`ProxyRequest`] / [`ProxyResponse`] generics that the core uses.
10//!
11//! **Protocol support**
12//! Uses `hyper_util::server::conn::auto::Builder`, so the same
13//! connection transparently handles both HTTP/1.1 *and* HTTP/2.
14//!
15//! ## Body streaming
16//! Inbound bodies are **streamed** straight into the upstream connection; no
17//! intermediate buffering beyond the configured `server.body_limit` takes
18//! place.  This prevents unbounded memory usage when clients upload large
19//! files but still gives you a safety-valve.
20
21mod health;
22#[cfg(feature = "swagger-ui")]
23pub mod swagger;
24#[cfg(test)]
25#[path = "../../tests/unit/server/tests.rs"]
26mod tests;
27
28use crate::core::{HttpMethod, ProxyCore, ProxyError, ProxyRequest, ProxyResponse, RequestContext};
29use crate::logging::config::LoggingConfig;
30use crate::logging::middleware::LoggingMiddleware;
31#[cfg(feature = "swagger-ui")]
32use crate::server::swagger::SwaggerUIConfig;
33use crate::{debug_fmt, error_fmt, info_fmt, trace_fmt, warn_fmt};
34use bytes::Bytes;
35use futures_util::TryStreamExt;
36use health::HealthServer;
37use http_body_util::BodyExt;
38use hyper::body::Incoming;
39use hyper::service::service_fn;
40use hyper::{Request, Response};
41use hyper_util::rt::TokioExecutor;
42use hyper_util::rt::TokioIo;
43use hyper_util::server::conn::auto::Builder as AutoBuilder;
44use reqwest::Body;
45use reqwest::header::{HeaderMap, HeaderValue};
46use serde::{Deserialize, Serialize};
47#[cfg(feature = "opentelemetry")]
48use std::borrow::Cow;
49use std::collections::HashMap;
50use std::convert::Infallible;
51use std::net::SocketAddr;
52use std::sync::Arc;
53use std::time::Instant;
54use tokio::signal;
55use tokio::sync::RwLock;
56use tokio::sync::oneshot;
57use tokio::task::{Id, JoinSet};
58
59#[cfg(unix)]
60use tokio::signal::unix::{SignalKind, signal};
61
62#[cfg(feature = "opentelemetry")]
63use opentelemetry::{
64    Context, KeyValue, global,
65    trace::{Span, SpanBuilder, SpanKind, Status},
66    trace::{TraceContextExt, Tracer},
67};
68#[cfg(feature = "opentelemetry")]
69use opentelemetry_http::HeaderExtractor;
70#[cfg(feature = "opentelemetry")]
71use opentelemetry_semantic_conventions::attribute::{
72    HTTP_FLAVOR, HTTP_HOST, HTTP_METHOD, HTTP_REQUEST_CONTENT_LENGTH, HTTP_RESPONSE_STATUS_CODE,
73    HTTP_SCHEME, HTTP_URL, HTTP_USER_AGENT, NET_PEER_IP,
74};
75
76/// Configuration for the HTTP server.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct ServerConfig {
79    /// Host to bind to
80    #[serde(default = "default_host")]
81    pub(crate) host: String,
82
83    /// Port to listen on
84    #[serde(default = "default_port")]
85    pub(crate) port: u16,
86
87    /// Port to listen on for health/readiness checks
88    #[serde(default = "default_health_port")]
89    pub(crate) health_port: u16,
90}
91
92pub(crate) fn default_host() -> String {
93    "127.0.0.1".to_string()
94}
95
96pub(crate) fn default_port() -> u16 {
97    8080
98}
99
100pub(crate) fn default_health_port() -> u16 {
101    8081
102}
103
104impl Default for ServerConfig {
105    fn default() -> Self {
106        Self {
107            host: default_host(),
108            port: default_port(),
109            health_port: default_health_port(),
110        }
111    }
112}
113
114/// HTTP server for the proxy.
115#[derive(Debug, Clone)]
116pub struct ProxyServer {
117    /// Server configuration
118    pub(crate) config: ServerConfig,
119    /// Proxy core
120    core: Arc<ProxyCore>,
121    /// Shutdown senders for each connection task
122    shutdown_senders: Arc<RwLock<HashMap<Id, oneshot::Sender<()>>>>,
123    /// Logging middleware for request/response logging
124    logging_middleware: LoggingMiddleware,
125}
126
127impl ProxyServer {
128    /// Create a new proxy server with the given configuration and proxy core.
129    pub fn new(config: ServerConfig, core: Arc<ProxyCore>) -> Self {
130        // Get logging configuration or use defaults
131        let logging_config = match core.config.get::<LoggingConfig>("proxy.logging") {
132            Ok(Some(config)) => config,
133            _ => LoggingConfig::default(),
134        };
135
136        // Create logging middleware
137        let logging_middleware = LoggingMiddleware::new(logging_config);
138
139        Self {
140            config,
141            core,
142            shutdown_senders: Arc::new(RwLock::new(HashMap::new())),
143            logging_middleware,
144        }
145    }
146
147    /// Get access to the proxy core.
148    pub fn core(&self) -> &Arc<ProxyCore> {
149        &self.core
150    }
151
152    /// Setup signal handlers for graceful shutdown.
153    #[cfg(unix)]
154    fn setup_signal_handlers() -> Result<
155        (
156            impl std::future::Future<Output = Result<(), std::io::Error>>,
157            tokio::signal::unix::Signal,
158        ),
159        ProxyError,
160    > {
161        let ctrl_c = signal::ctrl_c();
162        let term_stream = signal(SignalKind::terminate())
163            .map_err(|e| ProxyError::Other(format!("Cannot install SIGTERM handler: {e}")))?;
164        Ok((ctrl_c, term_stream))
165    }
166
167    /// Setup signal handlers for graceful shutdown (Windows version).
168    #[cfg(not(unix))]
169    fn setup_signal_handlers()
170    -> Result<impl std::future::Future<Output = Result<(), std::io::Error>>, ProxyError> {
171        Ok(signal::ctrl_c())
172    }
173
174    /// Setup TCP listener for the server.
175    async fn setup_listener(&self) -> Result<tokio::net::TcpListener, ProxyError> {
176        let addr = format!("{}:{}", self.config.host, self.config.port)
177            .parse::<SocketAddr>()
178            .map_err(|e| ProxyError::Other(format!("Invalid server address: {e}")))?;
179
180        let listener = tokio::net::TcpListener::bind(addr)
181            .await
182            .map_err(|e| ProxyError::Other(format!("Failed to bind: {e}")))?;
183
184        info_fmt!("Server", "Foxy proxy listening on http://{}", addr);
185        Ok(listener)
186    }
187
188    /// Handle a new connection by spawning a task for it.
189    async fn handle_new_connection(
190        &self,
191        stream: tokio::net::TcpStream,
192        remote_addr: SocketAddr,
193        core: Arc<ProxyCore>,
194        shutdown_senders: Arc<RwLock<HashMap<Id, oneshot::Sender<()>>>>,
195        join_set: &mut JoinSet<()>,
196    ) {
197        let client_ip = remote_addr.ip().to_string();
198        let logging_middleware = self.logging_middleware.clone();
199        let (tx, rx) = oneshot::channel();
200        let shutdown_senders_clone = shutdown_senders.clone();
201
202        let handle = join_set.spawn(async move {
203            let task_id = tokio::task::id();
204
205            let service = service_fn(move |req: Request<Incoming>| {
206                debug_fmt!("Server", "Incoming over {:?}", &req.version());
207                handle_request(req, core.clone(), client_ip.clone(), logging_middleware.clone())
208            });
209            let io = TokioIo::new(stream);
210
211            let builder = AutoBuilder::new(TokioExecutor::new());
212
213            // Create the connection
214            let connection = builder.serve_connection(io, service);
215
216            // Pin the connection and enable graceful shutdown
217            let mut conn = std::pin::pin!(connection);
218
219            // Run the connection with graceful shutdown
220            tokio::select! {
221                res = &mut conn => {
222                    Self::handle_connection_result(res);
223                }
224                _ = rx => {
225                    debug_fmt!("Server", "Connection received shutdown signal, waiting for graceful close");
226                    conn.as_mut().graceful_shutdown();
227
228                    // Continue running the connection until it completes
229                    Self::handle_connection_result(conn.await);
230                }
231            }
232
233            // Clean up the shutdown sender for this task
234            shutdown_senders_clone.write().await.remove(&task_id);
235            debug_fmt!("Server", "Connection task {:?} completed", task_id);
236        });
237
238        // Store the shutdown sender for this task
239        shutdown_senders.write().await.insert(handle.id(), tx);
240    }
241
242    /// Handle the result of a connection operation.
243    fn handle_connection_result(res: Result<(), Box<dyn std::error::Error + Send + Sync>>) {
244        match res {
245            Ok(()) => debug_fmt!("Server", "Connection closed normally"),
246            Err(e) => {
247                // Check if it's a graceful close by examining the error message
248                let err_str = e.to_string();
249                if !err_str.contains("connection closed") && !err_str.contains("connection reset") {
250                    error_fmt!("Server", "Connection error: {}", e);
251                }
252            }
253        }
254    }
255
256    /// Perform graceful shutdown of all connections.
257    async fn graceful_shutdown(
258        &self,
259        mut join_set: JoinSet<()>,
260        shutdown_senders: Arc<RwLock<HashMap<Id, oneshot::Sender<()>>>>,
261    ) -> Result<(), ProxyError> {
262        // Stop accepting connections and signal existing ones to shut down
263        info_fmt!(
264            "Server",
265            "Shutting down; waiting for {} connection(s)",
266            join_set.len()
267        );
268
269        // Signal all connections to close gracefully
270        {
271            let mut senders = shutdown_senders.write().await;
272            info_fmt!(
273                "Server",
274                "Signaling {} connections to shut down",
275                senders.len()
276            );
277            for (task_id, sender) in senders.drain() {
278                debug_fmt!("Server", "Sending shutdown signal to task {:?}", task_id);
279                let _ = sender.send(());
280            }
281        }
282
283        // Wait for connections to complete gracefully with a timeout
284        let shutdown_timeout = tokio::time::Duration::from_secs(30);
285        let start_time = tokio::time::Instant::now();
286
287        let shutdown_future = async {
288            let mut completed = 0;
289            let total = join_set.len();
290
291            while let Some(res) = join_set.join_next().await {
292                completed += 1;
293                match res {
294                    Ok(_) => debug_fmt!(
295                        "Server",
296                        "Connection task completed ({}/{})",
297                        completed,
298                        total
299                    ),
300                    Err(e) if e.is_cancelled() => debug_fmt!(
301                        "Server",
302                        "Connection task cancelled ({}/{})",
303                        completed,
304                        total
305                    ),
306                    Err(e) => error_fmt!(
307                        "Server",
308                        "Connection task failed ({}/{}): {}",
309                        completed,
310                        total,
311                        e
312                    ),
313                }
314
315                let elapsed = start_time.elapsed();
316                if completed % 10 == 0 || total - completed < 10 {
317                    info_fmt!(
318                        "Server",
319                        "Shutdown progress: {}/{} connections closed (elapsed: {:.1}s)",
320                        completed,
321                        total,
322                        elapsed.as_secs_f32()
323                    );
324                }
325            }
326        };
327
328        match tokio::time::timeout(shutdown_timeout, shutdown_future).await {
329            Ok(_) => {
330                let elapsed = start_time.elapsed();
331                info_fmt!(
332                    "Server",
333                    "All connections drained gracefully in {:.1}s",
334                    elapsed.as_secs_f32()
335                );
336            }
337            Err(_) => {
338                warn_fmt!(
339                    "Server",
340                    "Shutdown timed out after {} seconds, some connections may be forcefully closed",
341                    shutdown_timeout.as_secs()
342                );
343                // Cancel remaining tasks
344                join_set.shutdown().await;
345            }
346        }
347
348        info_fmt!("Server", "Shutdown complete");
349        Ok(())
350    }
351
352    /// Start the proxy server.
353    pub async fn start(&self) -> Result<(), ProxyError> {
354        // Setup listener
355        let listener = self.setup_listener().await?;
356
357        // Setup health server
358        let health_server = HealthServer::new(self.config.health_port);
359        health_server.set_ready();
360
361        // Setup signal handlers
362        #[cfg(unix)]
363        let (ctrl_c, mut term_stream) = Self::setup_signal_handlers()?;
364        #[cfg(not(unix))]
365        let ctrl_c = Self::setup_signal_handlers()?;
366
367        // Build the actual future that we'll await
368        #[cfg(unix)]
369        let sigterm = term_stream.recv();
370        #[cfg(not(unix))]
371        let sigterm = std::future::pending();
372
373        // Pin them on the stack so select! can poll them
374        tokio::pin!(ctrl_c);
375        tokio::pin!(sigterm);
376
377        // Create and use the shared shutdown senders map
378        let shutdown_senders = self.shutdown_senders.clone();
379
380        // Track spawned connection tasks
381        let mut join_set = JoinSet::new();
382        let core = self.core.clone();
383
384        // Flag to indicate shutdown has been initiated
385        let shutdown_initiated = Arc::new(std::sync::atomic::AtomicBool::new(false));
386        let shutdown_initiated_clone = shutdown_initiated.clone();
387
388        // Main server loop
389        loop {
390            tokio::select! {
391                _ = &mut ctrl_c => {
392                    info_fmt!("Server", "Received Ctrl-C; initiating graceful shutdown");
393                    shutdown_initiated_clone.store(true, std::sync::atomic::Ordering::SeqCst);
394                    break;
395                }
396                _ = &mut sigterm => {
397                    info_fmt!("Server", "Received SIGTERM; initiating graceful shutdown");
398                    shutdown_initiated_clone.store(true, std::sync::atomic::Ordering::SeqCst);
399                    break;
400                }
401                accept = listener.accept() => {
402                    match accept {
403                        Ok((stream, remote_addr)) => {
404                            // If shutdown has been initiated, reject new connections
405                            if shutdown_initiated.load(std::sync::atomic::Ordering::SeqCst) {
406                                info_fmt!("Server", "Rejecting new connection during shutdown");
407                                continue;
408                            }
409
410                            self.handle_new_connection(
411                                stream,
412                                remote_addr,
413                                core.clone(),
414                                shutdown_senders.clone(),
415                                &mut join_set,
416                            ).await;
417                        }
418                        Err(e) => error_fmt!("Server", "Accept error: {}", e),
419                    }
420                }
421            }
422        }
423
424        // Perform graceful shutdown
425        self.graceful_shutdown(join_set, shutdown_senders).await?;
426
427        // Ensure health server is also shut down
428        drop(health_server);
429
430        Ok(())
431    }
432}
433
434/// Validate HTTP headers to prevent request smuggling attacks.
435///
436/// This function checks for dangerous header combinations and malformed headers
437/// that could be used in HTTP request smuggling attacks.
438pub fn validate_headers(headers: &mut HeaderMap) -> Result<(), ProxyError> {
439    // SECURITY: Check for conflicting Content-Length and Transfer-Encoding headers
440    let has_content_length = headers.contains_key("content-length");
441    let has_transfer_encoding = headers.contains_key("transfer-encoding");
442
443    if has_content_length && has_transfer_encoding {
444        // RFC 7230 Section 3.3.3: If a message is received with both a Transfer-Encoding
445        // and a Content-Length header field, the Transfer-Encoding overrides the Content-Length.
446        // However, this is a common vector for request smuggling attacks.
447        warn_fmt!(
448            "Server",
449            "Request contains both Content-Length and Transfer-Encoding headers - removing Content-Length to prevent request smuggling"
450        );
451        headers.remove("content-length");
452    }
453
454    // SECURITY: Validate Transfer-Encoding header values
455    if let Some(te_header) = headers.get("transfer-encoding") {
456        if let Ok(te_value) = te_header.to_str() {
457            // Check for dangerous Transfer-Encoding values
458            let te_lower = te_value.to_lowercase();
459            if te_lower.contains("chunked") && te_lower != "chunked" {
460                // Transfer-Encoding with chunked and other values can cause confusion
461                warn_fmt!(
462                    "Server",
463                    "Suspicious Transfer-Encoding header value: {} - normalizing to 'chunked'",
464                    te_value
465                );
466                headers.insert("transfer-encoding", HeaderValue::from_static("chunked"));
467            }
468        }
469    }
470
471    // SECURITY: Check for CRLF injection in header values
472    for (name, value) in headers.iter() {
473        if let Ok(value_str) = value.to_str() {
474            if value_str.contains('\r') || value_str.contains('\n') {
475                let err = ProxyError::SecurityError(format!(
476                    "CRLF injection detected in header '{name}': contains newline characters"
477                ));
478                warn_fmt!("Server", "{}", err);
479                return Err(err);
480            }
481        }
482    }
483
484    // SECURITY: Check for multiple Host headers
485    let host_headers: Vec<_> = headers.get_all("host").iter().collect();
486    if host_headers.len() > 1 {
487        let err = ProxyError::SecurityError(
488            "Multiple Host headers detected - potential request smuggling attack".to_string(),
489        );
490        warn_fmt!("Server", "{}", err);
491        return Err(err);
492    }
493
494    // SECURITY: Validate Content-Length header format
495    if let Some(cl_header) = headers.get("content-length") {
496        if let Ok(cl_value) = cl_header.to_str() {
497            // Check for multiple Content-Length values (comma-separated)
498            if cl_value.contains(',') {
499                let err = ProxyError::SecurityError(
500                    "Multiple Content-Length values detected - potential request smuggling attack"
501                        .to_string(),
502                );
503                warn_fmt!("Server", "{}", err);
504                return Err(err);
505            }
506
507            // Validate that Content-Length is a valid number
508            if cl_value.parse::<u64>().is_err() {
509                let err = ProxyError::SecurityError(format!(
510                    "Invalid Content-Length header value: {cl_value}"
511                ));
512                warn_fmt!("Server", "{}", err);
513                return Err(err);
514            }
515        }
516    }
517
518    Ok(())
519}
520
521/// Convert a hyper request to a proxy request.
522async fn convert_hyper_request(
523    req: Request<Incoming>,
524    client_ip: String,
525) -> Result<ProxyRequest, ProxyError> {
526    let method = HttpMethod::from(req.method());
527    let uri = req.uri().clone();
528    let path = uri.path().to_owned();
529    let query = uri.query().map(|q| q.to_owned());
530    let mut headers = req.headers().clone();
531
532    trace_fmt!(
533        "Server",
534        "Converting request: {} {} with {} headers",
535        method,
536        path,
537        headers.len()
538    );
539
540    // SECURITY: Validate headers to prevent request smuggling attacks
541    validate_headers(&mut headers)?;
542
543    // Incoming → Stream → reqwest::Body
544    let hyper_stream = req.into_body().into_data_stream();
545    let byte_stream = hyper_stream.map_ok(Bytes::from);
546    let body = reqwest::Body::wrap_stream(byte_stream);
547
548    Ok(ProxyRequest {
549        method,
550        path,
551        query,
552        headers,
553        body,
554        context: Arc::new(RwLock::new(RequestContext {
555            client_ip: Some(client_ip),
556            start_time: Some(std::time::Instant::now()),
557            attributes: std::collections::HashMap::new(),
558        })),
559        custom_target: None,
560    })
561}
562
563/// Convert a proxy response to a hyper response.
564fn convert_proxy_response(resp: ProxyResponse) -> Result<Response<Body>, ProxyError> {
565    trace_fmt!(
566        "Server",
567        "Converting response with status {} and {} headers",
568        resp.status,
569        resp.headers.len()
570    );
571
572    let stream = resp.body.into_data_stream().map_err(|e| {
573        error_fmt!("Server", "Error streaming response body: {}", e);
574        std::io::Error::other(e)
575    });
576
577    let body = Body::wrap_stream(stream);
578
579    let mut builder = Response::builder().status(resp.status);
580    let mut_headers = builder.headers_mut().ok_or_else(|| {
581        error_fmt!(
582            "Server",
583            "Failed to get mutable headers from response builder"
584        );
585        ProxyError::Other("Failed to build response: unable to get mutable headers".into())
586    })?;
587    *mut_headers = resp.headers;
588
589    builder.body(body).map_err(|e| {
590        let err = ProxyError::Other(e.to_string());
591        error_fmt!("Server", "Failed to build response: {}", err);
592        err
593    })
594}
595
596/// Handle an incoming HTTP request.
597async fn handle_request(
598    req: Request<Incoming>,
599    core: Arc<ProxyCore>,
600    client_ip: String,
601    logging_middleware: LoggingMiddleware,
602) -> Result<Response<Body>, Infallible> {
603    // Process the request through the logging middleware
604    let remote_addr = req.extensions().get::<SocketAddr>().cloned();
605    let (req, request_info) = logging_middleware.process(req, remote_addr);
606
607    // Start Swagger UI Handling
608    #[cfg(feature = "swagger-ui")]
609    {
610        if let Ok(Some(swagger_config)) = core.config.get::<SwaggerUIConfig>("proxy.swagger_ui") {
611            if swagger_config.enabled
612                && (req.uri().path().eq(&swagger_config.path)
613                    || req.uri().path().starts_with(&swagger_config.path))
614            {
615                let swagger_response = swagger::handle_swagger_request(&req, &swagger_config)
616                    .await
617                    .unwrap();
618                return Ok(swagger_response);
619            }
620        }
621    }
622    // End Swagger UI Handling
623
624    // Start timing for upstream request
625    let upstream_start = Instant::now();
626    // ---------- OpenTelemetry SERVER span ----------
627    #[cfg(feature = "opentelemetry")]
628    let span_context = {
629        let method = req.method().as_str().to_owned();
630        let path = req.uri().path().to_owned();
631        let full_url = req.uri().clone().to_string();
632        let scheme = req.uri().scheme_str().unwrap_or("http").to_owned();
633        let host = req
634            .headers()
635            .get("host")
636            .and_then(|v| v.to_str().ok())
637            .unwrap_or("-")
638            .to_owned();
639        let http_version = match req.version() {
640            hyper::Version::HTTP_10 => "1.0",
641            hyper::Version::HTTP_11 => "1.1",
642            hyper::Version::HTTP_2 => "2",
643            hyper::Version::HTTP_3 => "3",
644            _ => "unknown",
645        };
646        let req_content_len = req
647            .headers()
648            .get("content-length")
649            .and_then(|v| v.to_str().ok())
650            .and_then(|s| s.parse::<i64>().ok())
651            .unwrap_or(0);
652        let user_agent = req
653            .headers()
654            .get("user-agent")
655            .and_then(|v| v.to_str().ok())
656            .unwrap_or("-")
657            .to_owned();
658        let peer_ip = client_ip.as_str().to_owned();
659
660        let context = extract_context_from_request(&req);
661        let mut span = global::tracer("foxy::proxy").build_with_context(
662            SpanBuilder {
663                name: Cow::from(format!("{method} {path}")),
664                span_kind: Some(SpanKind::Server),
665                ..Default::default()
666            },
667            &context,
668        );
669
670        span.set_attributes([
671            KeyValue::new(HTTP_METHOD, method),
672            KeyValue::new(HTTP_URL, full_url.clone()),
673            KeyValue::new(HTTP_SCHEME, scheme),
674            KeyValue::new(HTTP_HOST, host),
675            KeyValue::new(HTTP_FLAVOR, http_version),
676            KeyValue::new(HTTP_REQUEST_CONTENT_LENGTH, req_content_len),
677            KeyValue::new(HTTP_USER_AGENT, user_agent),
678            KeyValue::new(NET_PEER_IP, peer_ip),
679        ]);
680
681        context.with_span(span)
682    };
683
684    /* ---- convert Hyper → ProxyRequest ---- */
685    let method = req.method().clone();
686    let path = req.uri().path().to_owned();
687
688    debug_fmt!("Server", "Received request: {} {}", method, path);
689
690    let proxy_req = match convert_hyper_request(req, client_ip.clone()).await {
691        Ok(r) => r,
692        Err(e) => {
693            error_fmt!(
694                "Server",
695                "Failed to convert request {} {}: {}",
696                method,
697                path,
698                e
699            );
700            return Ok(Response::builder()
701                .status(500)
702                .body(Body::from("Internal Server Error"))
703                .unwrap());
704        }
705    };
706
707    // ---------- core processing ----------
708    #[cfg(feature = "opentelemetry")]
709    let span_clone = span_context.clone();
710
711    #[cfg(feature = "opentelemetry")]
712    let span_ref = span_context.span();
713
714    #[cfg(feature = "opentelemetry")]
715    let result = core.process_request(proxy_req, Some(span_clone)).await;
716
717    #[cfg(not(feature = "opentelemetry"))]
718    let result = core.process_request(proxy_req).await;
719
720    // ---------- finalise span ----------
721    #[cfg(feature = "opentelemetry")]
722    {
723        match result.as_ref() {
724            Ok(_r) => span_ref.set_status(Status::Ok),
725            Err(e) => {
726                span_ref.record_error(e);
727                span_ref.set_status(Status::Error {
728                    description: Cow::from(e.to_string()),
729                })
730            }
731        }
732    }
733
734    /* ---------- map response ---------- */
735    let response: Result<Response<Body>, Infallible> = match result {
736        Ok(proxy_resp) => {
737            debug_fmt!(
738                "Server",
739                "Successfully processed request {} {} -> {}",
740                method,
741                path,
742                proxy_resp.status
743            );
744            match convert_proxy_response(proxy_resp) {
745                Ok(resp) => {
746                    // Calculate upstream duration
747                    let upstream_duration = upstream_start.elapsed();
748
749                    // Log the response with timing information
750                    logging_middleware.log_response(&resp, &request_info, Some(upstream_duration));
751
752                    Ok(resp)
753                }
754                Err(e) => {
755                    error_fmt!(
756                        "Server",
757                        "Failed to convert response for {} {}: {}",
758                        method,
759                        path,
760                        e
761                    );
762                    Ok(Response::builder()
763                        .status(500)
764                        .body(Body::from("Internal Server Error"))
765                        .unwrap())
766                }
767            }
768        }
769        Err(e) => {
770            let (status, msg) = match &e {
771                ProxyError::Timeout(d) => {
772                    warn_fmt!(
773                        "Server",
774                        "Request {} {} timed out after {:?}",
775                        method,
776                        path,
777                        d
778                    );
779                    (504, format!("Gateway Timeout after {d:?}"))
780                }
781                ProxyError::RoutingError(msg) => {
782                    warn_fmt!("Server", "Routing error for {} {}: {}", method, path, msg);
783                    (404, "Route not found".into())
784                }
785                ProxyError::SecurityError(msg) => {
786                    warn_fmt!("Server", "Security error for {} {}: {}", method, path, msg);
787                    (403, "Forbidden".into())
788                }
789                ProxyError::ClientError(err) => {
790                    error_fmt!("Server", "Client error for {} {}: {}", method, path, err);
791                    (502, "Bad Gateway".into())
792                }
793                _ => {
794                    error_fmt!(
795                        "Server",
796                        "Internal error processing {} {}: {}",
797                        method,
798                        path,
799                        e
800                    );
801                    (500, "Internal Server Error".into())
802                }
803            };
804
805            Ok(Response::builder()
806                .status(status)
807                .body(Body::from(msg))
808                .unwrap())
809        }
810    };
811
812    // Log error responses too
813    #[allow(clippy::collapsible_if)]
814    if let Ok(resp) = &response {
815        if resp.status().is_client_error() || resp.status().is_server_error() {
816            logging_middleware.log_response(resp, &request_info, None);
817        }
818    }
819
820    #[cfg(feature = "opentelemetry")]
821    {
822        let status_code = response.as_ref().unwrap().status().as_u16();
823
824        span_ref.set_attribute(KeyValue::new(HTTP_RESPONSE_STATUS_CODE, status_code as i64));
825        span_ref.end();
826    }
827
828    response
829}
830
831// Utility function to extract the context from the incoming request headers
832#[cfg(feature = "opentelemetry")]
833fn extract_context_from_request(req: &Request<Incoming>) -> Context {
834    global::get_text_map_propagator(|propagator| {
835        propagator.extract(&HeaderExtractor(req.headers()))
836    })
837}