1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-28 01:32:57 +01:00

add ConnectionInfo tests

This commit is contained in:
Nikolay Kim 2017-12-05 21:38:52 -08:00
parent c3de32c3b3
commit d7e65b6212
6 changed files with 114 additions and 42 deletions

View File

@ -175,7 +175,7 @@ impl<T, H> Http1<T, H>
not_ready = false; not_ready = false;
// set remote addr // set remote addr
req.set_remove_addr(self.addr); req.set_peer_addr(self.addr);
// stop keepalive timer // stop keepalive timer
self.keepalive_timer.take(); self.keepalive_timer.take();

View File

@ -223,7 +223,7 @@ impl Entry {
parts.method, parts.uri, parts.version, parts.headers, payload); parts.method, parts.uri, parts.version, parts.headers, payload);
// set remote addr // set remote addr
req.set_remove_addr(addr); req.set_peer_addr(addr);
// Payload sender // Payload sender
let psender = PayloadType::new(req.headers(), psender); let psender = PayloadType::new(req.headers(), psender);

View File

@ -140,12 +140,27 @@ impl<S> HttpRequest<S> {
&self.0.headers &self.0.headers
} }
#[cfg(test)]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.as_mut().headers
}
/// The target path of this Request. /// The target path of this Request.
#[inline] #[inline]
pub fn path(&self) -> &str { pub fn path(&self) -> &str {
self.0.uri.path() 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. /// Load *ConnectionInfo* for currect request.
#[inline] #[inline]
pub fn load_connection_info(&mut self) -> &ConnectionInfo { pub fn load_connection_info(&mut self) -> &ConnectionInfo {
@ -157,19 +172,12 @@ impl<S> HttpRequest<S> {
self.0.info.as_ref().unwrap() 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] #[inline]
pub fn remote(&self) -> Option<&SocketAddr> { pub fn peer_addr(&self) -> Option<&SocketAddr> {
self.0.addr.as_ref() self.0.addr.as_ref()
} }
pub(crate) fn set_remove_addr(&mut self, addr: Option<SocketAddr>) { pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
self.as_mut().addr = addr self.as_mut().addr = addr
} }

View File

@ -2,6 +2,7 @@ use std::str::FromStr;
use http::header::{self, HeaderName}; use http::header::{self, HeaderName};
use httprequest::HttpRequest; use httprequest::HttpRequest;
const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR";
const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST"; const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST";
const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO"; const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO";
@ -13,9 +14,8 @@ const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO";
pub struct ConnectionInfo<'a> { pub struct ConnectionInfo<'a> {
scheme: &'a str, scheme: &'a str,
host: &'a str, host: &'a str,
remote: String, remote: Option<&'a str>,
forwarded_for: Vec<&'a str>, peer: Option<String>,
forwarded_by: Vec<&'a str>,
} }
impl<'a> ConnectionInfo<'a> { impl<'a> ConnectionInfo<'a> {
@ -24,20 +24,21 @@ impl<'a> ConnectionInfo<'a> {
pub fn new<S>(req: &'a HttpRequest<S>) -> ConnectionInfo<'a> { pub fn new<S>(req: &'a HttpRequest<S>) -> ConnectionInfo<'a> {
let mut host = None; let mut host = None;
let mut scheme = None; let mut scheme = None;
let mut forwarded_for = Vec::new(); let mut remote = None;
let mut forwarded_by = Vec::new(); let mut peer = None;
// load forwarded header // load forwarded header
for hdr in req.headers().get_all(header::FORWARDED) { for hdr in req.headers().get_all(header::FORWARDED) {
if let Ok(val) = hdr.to_str() { if let Ok(val) = hdr.to_str() {
for pair in val.split(';') { for pair in val.split(';') {
for el in pair.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(name) = items.next() {
if let Some(val) = items.next() { if let Some(val) = items.next() {
match &name.to_lowercase() as &str { match &name.to_lowercase() as &str {
"for" => forwarded_for.push(val.trim()), "for" => if remote.is_none() {
"by" => forwarded_by.push(val.trim()), remote = Some(val.trim());
},
"proto" => if scheme.is_none() { "proto" => if scheme.is_none() {
scheme = Some(val.trim()); 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 { ConnectionInfo {
scheme: scheme.unwrap_or("http"), scheme: scheme.unwrap_or("http"),
host: host.unwrap_or("localhost"), host: host.unwrap_or("localhost"),
remote: String::new(), remote: remote,
forwarded_for: forwarded_for, peer: peer,
forwarded_by: forwarded_by,
} }
} }
@ -130,19 +146,66 @@ impl<'a> ConnectionInfo<'a> {
/// - X-Forwarded-For /// - X-Forwarded-For
/// - peername of opened socket /// - peername of opened socket
#[inline] #[inline]
pub fn remote(&self) -> &str { pub fn remote(&self) -> Option<&str> {
&self.remote if let Some(r) = self.remote {
} Some(r)
} else if let Some(ref peer) = self.peer {
/// List of the nodes making the request to the proxy. Some(peer)
#[inline] } else {
pub fn forwarded_for(&self) -> &Vec<&str> { None
&self.forwarded_for }
} }
}
/// List of the user-agent facing interface of the proxies
#[inline] #[cfg(test)]
pub fn forwarded_by(&self) -> &Vec<&str> { mod tests {
&self.forwarded_by 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");
} }
} }

View File

@ -102,6 +102,7 @@ impl Logger {
impl Middleware for Logger { impl Middleware for Logger {
fn start(&self, req: &mut HttpRequest) -> Started { fn start(&self, req: &mut HttpRequest) -> Started {
req.load_connection_info();
req.extensions().insert(StartTime(time::now())); req.extensions().insert(StartTime(time::now()));
Started::Done Started::Done
} }
@ -112,7 +113,6 @@ impl Middleware for Logger {
} }
} }
/// A formatting style for the `Logger`, consisting of multiple /// A formatting style for the `Logger`, consisting of multiple
/// `FormatText`s concatenated into one line. /// `FormatText`s concatenated into one line.
#[derive(Clone)] #[derive(Clone)]
@ -237,11 +237,12 @@ impl FormatText {
fmt.write_fmt(format_args!("{:.6}", response_time_ms)) fmt.write_fmt(format_args!("{:.6}", response_time_ms))
}, },
FormatText::RemoteAddr => { FormatText::RemoteAddr => {
if let Some(addr) = req.remote() { if let Some(addr) = req.connection_info() {
addr.fmt(fmt) if let Some(remote) = addr.remote() {
} else { return remote.fmt(fmt);
"-".fmt(fmt) }
} }
"-".fmt(fmt)
} }
FormatText::RequestTime => { FormatText::RequestTime => {
entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]") entry_time.strftime("[%d/%b/%Y:%H:%M:%S %z]")

View File

@ -395,7 +395,7 @@ mod tests {
("/v{val}/{val2}/index.html", None, 4), ("/v{val}/{val2}/index.html", None, 4),
("/v/{tail:.*}", None, 5), ("/v/{tail:.*}", None, 5),
]; ];
let mut rec = RouteRecognizer::new("/", routes); let rec = RouteRecognizer::new("/", routes);
let (params, val) = rec.recognize("/name").unwrap(); let (params, val) = rec.recognize("/name").unwrap();
assert_eq!(*val, 1); assert_eq!(*val, 1);