From d7e65b62122b50e1d662ec570cfe0e1a286237bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 21:38:52 -0800 Subject: [PATCH] add ConnectionInfo tests --- src/h1.rs | 2 +- src/h2.rs | 2 +- src/httprequest.rs | 26 ++++++--- src/info.rs | 113 +++++++++++++++++++++++++++++--------- src/middlewares/logger.rs | 11 ++-- src/recognizer.rs | 2 +- 6 files changed, 114 insertions(+), 42 deletions(-) diff --git a/src/h1.rs b/src/h1.rs index 1222eb61..b7fed38c 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -175,7 +175,7 @@ impl Http1 not_ready = false; // set remote addr - req.set_remove_addr(self.addr); + req.set_peer_addr(self.addr); // stop keepalive timer self.keepalive_timer.take(); diff --git a/src/h2.rs b/src/h2.rs index 929fb992..cf89a719 100644 --- a/src/h2.rs +++ b/src/h2.rs @@ -223,7 +223,7 @@ impl Entry { parts.method, parts.uri, parts.version, parts.headers, payload); // set remote addr - req.set_remove_addr(addr); + req.set_peer_addr(addr); // Payload sender let psender = PayloadType::new(req.headers(), psender); diff --git a/src/httprequest.rs b/src/httprequest.rs index 75aa8803..3c30037e 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -140,12 +140,27 @@ impl HttpRequest { &self.0.headers } + #[cfg(test)] + pub fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.as_mut().headers + } + /// The target path of this Request. #[inline] pub fn path(&self) -> &str { self.0.uri.path() } + /// Get previously loaded *ConnectionInfo*. + #[inline] + pub fn connection_info(&self) -> Option<&ConnectionInfo> { + if self.0.info.is_none() { + None + } else { + self.0.info.as_ref() + } + } + /// Load *ConnectionInfo* for currect request. #[inline] pub fn load_connection_info(&mut self) -> &ConnectionInfo { @@ -157,19 +172,12 @@ impl HttpRequest { self.0.info.as_ref().unwrap() } - /// Remote IP of client initiated HTTP request. - /// - /// The IP is resolved through the following headers, in this order: - /// - /// - Forwarded - /// - X-Forwarded-For - /// - peername of opened socket #[inline] - pub fn remote(&self) -> Option<&SocketAddr> { + pub fn peer_addr(&self) -> Option<&SocketAddr> { self.0.addr.as_ref() } - pub(crate) fn set_remove_addr(&mut self, addr: Option) { + pub(crate) fn set_peer_addr(&mut self, addr: Option) { self.as_mut().addr = addr } diff --git a/src/info.rs b/src/info.rs index 76420ee6..a15b6211 100644 --- a/src/info.rs +++ b/src/info.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use http::header::{self, HeaderName}; use httprequest::HttpRequest; +const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR"; const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST"; const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; @@ -13,9 +14,8 @@ const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; pub struct ConnectionInfo<'a> { scheme: &'a str, host: &'a str, - remote: String, - forwarded_for: Vec<&'a str>, - forwarded_by: Vec<&'a str>, + remote: Option<&'a str>, + peer: Option, } impl<'a> ConnectionInfo<'a> { @@ -24,20 +24,21 @@ impl<'a> ConnectionInfo<'a> { pub fn new(req: &'a HttpRequest) -> ConnectionInfo<'a> { let mut host = None; let mut scheme = None; - let mut forwarded_for = Vec::new(); - let mut forwarded_by = Vec::new(); + let mut remote = None; + let mut peer = None; // load forwarded header for hdr in req.headers().get_all(header::FORWARDED) { if let Ok(val) = hdr.to_str() { for pair in val.split(';') { for el in pair.split(',') { - let mut items = el.splitn(1, '='); + let mut items = el.trim().splitn(2, '='); if let Some(name) = items.next() { if let Some(val) = items.next() { match &name.to_lowercase() as &str { - "for" => forwarded_for.push(val.trim()), - "by" => forwarded_by.push(val.trim()), + "for" => if remote.is_none() { + remote = Some(val.trim()); + }, "proto" => if scheme.is_none() { scheme = Some(val.trim()); }, @@ -89,12 +90,27 @@ impl<'a> ConnectionInfo<'a> { } } + // remote addr + if remote.is_none() { + if let Some(h) = req.headers().get( + HeaderName::from_str(X_FORWARDED_FOR).unwrap()) { + if let Ok(h) = h.to_str() { + remote = h.split(',').next().map(|v| v.trim()); + } + } + if remote.is_none() { + if let Some(addr) = req.peer_addr() { + // get peeraddr from socketaddr + peer = Some(format!("{}", addr)); + } + } + } + ConnectionInfo { scheme: scheme.unwrap_or("http"), host: host.unwrap_or("localhost"), - remote: String::new(), - forwarded_for: forwarded_for, - forwarded_by: forwarded_by, + remote: remote, + peer: peer, } } @@ -130,19 +146,66 @@ impl<'a> ConnectionInfo<'a> { /// - X-Forwarded-For /// - peername of opened socket #[inline] - pub fn remote(&self) -> &str { - &self.remote - } - - /// List of the nodes making the request to the proxy. - #[inline] - pub fn forwarded_for(&self) -> &Vec<&str> { - &self.forwarded_for - } - - /// List of the user-agent facing interface of the proxies - #[inline] - pub fn forwarded_by(&self) -> &Vec<&str> { - &self.forwarded_by + pub fn remote(&self) -> Option<&str> { + if let Some(r) = self.remote { + Some(r) + } else if let Some(ref peer) = self.peer { + Some(peer) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::header::HeaderValue; + + #[test] + fn test_forwarded() { + let req = HttpRequest::default(); + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "http"); + assert_eq!(info.host(), "localhost"); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + header::FORWARDED, + HeaderValue::from_static( + "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org")); + + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "https"); + assert_eq!(info.host(), "rust-lang.org"); + assert_eq!(info.remote(), Some("192.0.2.60")); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + header::HOST, HeaderValue::from_static("rust-lang.org")); + + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "http"); + assert_eq!(info.host(), "rust-lang.org"); + assert_eq!(info.remote(), None); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + HeaderName::from_str(X_FORWARDED_FOR).unwrap(), HeaderValue::from_static("192.0.2.60")); + let info = ConnectionInfo::new(&req); + assert_eq!(info.remote(), Some("192.0.2.60")); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + HeaderName::from_str(X_FORWARDED_HOST).unwrap(), HeaderValue::from_static("192.0.2.60")); + let info = ConnectionInfo::new(&req); + assert_eq!(info.host(), "192.0.2.60"); + assert_eq!(info.remote(), None); + + let mut req = HttpRequest::default(); + req.headers_mut().insert( + HeaderName::from_str(X_FORWARDED_PROTO).unwrap(), HeaderValue::from_static("https")); + let info = ConnectionInfo::new(&req); + assert_eq!(info.scheme(), "https"); } } diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index 92117ff0..51667a22 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -102,6 +102,7 @@ impl Logger { impl Middleware for Logger { fn start(&self, req: &mut HttpRequest) -> Started { + req.load_connection_info(); req.extensions().insert(StartTime(time::now())); Started::Done } @@ -112,7 +113,6 @@ impl Middleware for Logger { } } - /// A formatting style for the `Logger`, consisting of multiple /// `FormatText`s concatenated into one line. #[derive(Clone)] @@ -237,11 +237,12 @@ impl FormatText { fmt.write_fmt(format_args!("{:.6}", response_time_ms)) }, FormatText::RemoteAddr => { - if let Some(addr) = req.remote() { - addr.fmt(fmt) - } else { - "-".fmt(fmt) + if let Some(addr) = req.connection_info() { + if let Some(remote) = addr.remote() { + return remote.fmt(fmt); + } } + "-".fmt(fmt) } FormatText::RequestTime => { entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]") diff --git a/src/recognizer.rs b/src/recognizer.rs index 9776f395..3fb34330 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -395,7 +395,7 @@ mod tests { ("/v{val}/{val2}/index.html", None, 4), ("/v/{tail:.*}", None, 5), ]; - let mut rec = RouteRecognizer::new("/", routes); + let rec = RouteRecognizer::new("/", routes); let (params, val) = rec.recognize("/name").unwrap(); assert_eq!(*val, 1);