From 42464d1d49f0cf281721751766957ec63a59ecd0 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sat, 5 Jul 2025 13:21:20 +0900 Subject: [PATCH] simplified http_log internal routine --- rpxy-lib/src/message_handler/http_log.rs | 91 ++++++++++++++++--- rpxy-lib/src/message_handler/utils_headers.rs | 31 ++++--- 2 files changed, 94 insertions(+), 28 deletions(-) diff --git a/rpxy-lib/src/message_handler/http_log.rs b/rpxy-lib/src/message_handler/http_log.rs index 40d13cc..5ae302e 100644 --- a/rpxy-lib/src/message_handler/http_log.rs +++ b/rpxy-lib/src/message_handler/http_log.rs @@ -1,5 +1,5 @@ use super::canonical_address::ToCanonical; -use crate::log::*; +use crate::{log::*, message_handler::utils_headers}; use http::header; use std::net::SocketAddr; @@ -12,10 +12,11 @@ pub struct HttpMessageLog { pub host: String, pub p_and_q: String, pub version: http::Version, - pub uri_scheme: String, - pub uri_host: String, + pub scheme: String, + pub path: String, pub ua: String, pub xff: String, + pub forwarded: String, pub status: String, pub upstream: String, } @@ -29,17 +30,21 @@ impl From<&http::Request> for HttpMessageLog { .map_or_else(|| "", |s| s.to_str().unwrap_or("")) .to_string() }; + let host = + utils_headers::host_from_uri_or_host_header(req.uri(), req.headers().get(header::HOST).cloned()).unwrap_or_default(); + Self { // tls_server_name: "".to_string(), client_addr: "".to_string(), method: req.method().to_string(), - host: header_mapper(header::HOST), + host, p_and_q: req.uri().path_and_query().map_or_else(|| "", |v| v.as_str()).to_string(), version: req.version(), - uri_scheme: req.uri().scheme_str().unwrap_or("").to_string(), - uri_host: req.uri().host().unwrap_or("").to_string(), + scheme: req.uri().scheme_str().unwrap_or("").to_string(), + path: req.uri().path().to_string(), ua: header_mapper(header::USER_AGENT), xff: header_mapper(header::HeaderName::from_static("x-forwarded-for")), + forwarded: header_mapper(header::FORWARDED), status: "".to_string(), upstream: "".to_string(), } @@ -48,26 +53,29 @@ impl From<&http::Request> for HttpMessageLog { impl std::fmt::Display for HttpMessageLog { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let forwarded_part = if !self.forwarded.is_empty() { + format!(" \"{}\"", self.forwarded) + } else { + "".to_string() + }; + write!( f, - "{} <- {} -- {} {} {:?} -- {} -- {} \"{}\", \"{}\" \"{}\"", - if !self.host.is_empty() { - self.host.as_str() - } else { - self.uri_host.as_str() - }, + "{} <- {} -- {} {} {:?} -- {} -- {} \"{}\", \"{}\"{} \"{}\"", + self.host, self.client_addr, self.method, self.p_and_q, self.version, self.status, - if !self.uri_scheme.is_empty() && !self.uri_host.is_empty() { - format!("{}://{}", self.uri_scheme, self.uri_host) + if !self.scheme.is_empty() && !self.host.is_empty() { + format!("{}://{}{}", self.scheme, self.host, self.path) } else { - "".to_string() + self.path.clone() }, self.ua, self.xff, + forwarded_part, self.upstream ) } @@ -102,3 +110,56 @@ impl HttpMessageLog { ); } } + +#[cfg(test)] +mod tests { + use super::*; + use http::{Method, Version}; + + #[test] + fn test_log_format_without_forwarded() { + let log = HttpMessageLog { + client_addr: "192.168.1.1:8080".to_string(), + method: Method::GET.to_string(), + host: "example.com".to_string(), + p_and_q: "/path?query=value".to_string(), + version: Version::HTTP_11, + scheme: "https".to_string(), + path: "/path".to_string(), + ua: "Mozilla/5.0".to_string(), + xff: "10.0.0.1".to_string(), + forwarded: "".to_string(), + status: "200".to_string(), + upstream: "https://backend.example.com".to_string(), + }; + + let formatted = format!("{}", log); + assert!(!formatted.contains(" \"\"")); + assert!(formatted.contains("\"Mozilla/5.0\", \"10.0.0.1\" \"https://backend.example.com\"")); + } + + #[test] + fn test_log_format_with_forwarded() { + let log = HttpMessageLog { + client_addr: "192.168.1.1:8080".to_string(), + method: Method::GET.to_string(), + host: "example.com".to_string(), + p_and_q: "/path?query=value".to_string(), + version: Version::HTTP_11, + scheme: "https".to_string(), + path: "/path".to_string(), + ua: "Mozilla/5.0".to_string(), + xff: "10.0.0.1".to_string(), + forwarded: "for=192.0.2.60;proto=http;by=203.0.113.43".to_string(), + status: "200".to_string(), + upstream: "https://backend.example.com".to_string(), + }; + + let formatted = format!("{}", log); + assert!(formatted.contains(" \"for=192.0.2.60;proto=http;by=203.0.113.43\"")); + assert!( + formatted + .contains("\"Mozilla/5.0\", \"10.0.0.1\" \"for=192.0.2.60;proto=http;by=203.0.113.43\" \"https://backend.example.com\"") + ); + } +} diff --git a/rpxy-lib/src/message_handler/utils_headers.rs b/rpxy-lib/src/message_handler/utils_headers.rs index c4a0b75..ddc68ba 100644 --- a/rpxy-lib/src/message_handler/utils_headers.rs +++ b/rpxy-lib/src/message_handler/utils_headers.rs @@ -360,25 +360,30 @@ fn generate_forwarded_header(headers: &HeaderMap, tls: bool, original_uri: &Uri) "for={};proto={};host={}", for_values, if tls { "https" } else { "http" }, - host_from_uri(original_uri)? + host_from_uri_or_host_header(original_uri, headers.get(header::HOST).cloned())? ); Ok(forwarded_value) } -#[inline] /// Extract host from URI -fn host_from_uri(uri: &Uri) -> Result { - uri - .host() - .map(|host| { - if let Some(port) = uri.port_u16() { - format!("{}:{}", host, port) - } else { - host.to_string() - } - }) - .ok_or_else(|| anyhow!("No host found in URI")) +pub(super) fn host_from_uri_or_host_header(uri: &Uri, host_header_value: Option) -> Result { + // Prioritize uri host over host header + let uri_host = uri.host().map(|host| { + if let Some(port) = uri.port_u16() { + format!("{}:{}", host, port) + } else { + host.to_string() + } + }); + if let Some(host) = uri_host { + return Ok(host); + } + // If uri host is not available, use host header + host_header_value + .map(|h| h.to_str().map(|s| s.to_string())) + .transpose()? + .ok_or_else(|| anyhow!("No host found in URI or Host header")) } /// Remove connection header