diff --git a/proxy/src/bind.rs b/proxy/src/bind.rs index 4955d4c40..223a15740 100644 --- a/proxy/src/bind.rs +++ b/proxy/src/bind.rs @@ -67,7 +67,16 @@ pub enum Binding { /// that each host receives its own connection. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Protocol { - Http1(Host), + Http1 { + host: Host, + /// Whether or not the request URI was in absolute form. + /// + /// This is used to configure Hyper's behaviour at the connection + /// level, so it's necessary that requests with and without + /// absolute URIs be bound to separate service stacks. It is also + /// used to determine what URI normalization will be necessary. + was_absolute_form: bool, + }, Http2 } @@ -87,7 +96,8 @@ pub enum Host { /// request's original destination according to `SO_ORIGINAL_DST`. #[derive(Copy, Clone, Debug)] pub struct NormalizeUri { - inner: S + inner: S, + was_absolute_form: bool, } pub type Service = Binding; @@ -209,7 +219,7 @@ where // Rewrite the HTTP/1 URI, if the authorities in the Host header // and request URI are not in agreement, or are not present. - let proxy = NormalizeUri::new(sensors); + let proxy = NormalizeUri::new(sensors, protocol.was_absolute_form()); // Automatically perform reconnects if the connection fails. // @@ -264,8 +274,8 @@ where impl NormalizeUri { - fn new (inner: S) -> Self { - Self { inner } + fn new(inner: S, was_absolute_form: bool) -> Self { + Self { inner, was_absolute_form } } } @@ -292,7 +302,15 @@ where fn(S::Service) -> NormalizeUri >; fn new_service(&self) -> Self::Future { - self.inner.new_service().map(NormalizeUri::new) + let s = self.inner.new_service(); + // This weird dance is so that the closure doesn't have to + // capture `self` and can just be a `fn` (so the `Map`) + // can be returned unboxed. + if self.was_absolute_form { + s.map(|inner| NormalizeUri::new(inner, true)) + } else { + s.map(|inner| NormalizeUri::new(inner, false)) + } } } @@ -315,12 +333,11 @@ where fn call(&mut self, mut request: S::Request) -> Self::Future { if request.version() != http::Version::HTTP_2 { - h1::normalize_our_view_of_uri(&mut request); + h1::normalize_our_view_of_uri(&mut request, self.was_absolute_form); } self.inner.call(request) } } - // ===== impl Binding ===== impl tower::Service for Binding { @@ -369,20 +386,35 @@ impl Protocol { return Protocol::Http2 } + let authority_part = req.uri().authority_part(); + let was_absolute_form = authority_part.is_some(); + trace!( + "Protocol::detect(); req.uri='{:?}'; was_absolute_form={:?};", + req.uri(), was_absolute_form + ); // If the request has an authority part, use that as the host part of // the key for an HTTP/1.x request. - let host = req.uri().authority_part() + let host = authority_part .cloned() .or_else(|| h1::authority_from_host(req)) .map(Host::Authority) .unwrap_or_else(|| Host::NoAuthority); - Protocol::Http1(host) + + Protocol::Http1 { host, was_absolute_form } + } + + /// Returns true if the request was originally received in absolute form. + pub fn was_absolute_form(&self) -> bool { + match self { + &Protocol::Http1 { was_absolute_form, .. } => was_absolute_form, + _ => false, + } } pub fn can_reuse_clients(&self) -> bool { match *self { - Protocol::Http2 | Protocol::Http1(Host::Authority(_)) => true, + Protocol::Http2 | Protocol::Http1 { host: Host::Authority(_), .. } => true, _ => false, } } diff --git a/proxy/src/inbound.rs b/proxy/src/inbound.rs index 6318234d1..d42daa2f1 100644 --- a/proxy/src/inbound.rs +++ b/proxy/src/inbound.rs @@ -72,7 +72,6 @@ where let key = key.map(move |addr| (addr, proto)); trace!("recognize key={:?}", key); - key } @@ -115,6 +114,14 @@ mod tests { Inbound::new(default, bind) } + fn make_key_http1(addr: net::SocketAddr) -> (net::SocketAddr, bind::Protocol) { + let protocol = bind::Protocol::Http1 { + host: Host::NoAuthority, + was_absolute_form: false, + }; + (addr, protocol) + } + quickcheck! { fn recognize_orig_dst( orig_dst: net::SocketAddr, @@ -127,9 +134,7 @@ mod tests { let srv_ctx = ctx::transport::Server::new(&ctx, &local, &remote, &Some(orig_dst)); - let rec = srv_ctx.orig_dst_if_not_local().map(|addr| - (addr, bind::Protocol::Http1(Host::NoAuthority)) - ); + let rec = srv_ctx.orig_dst_if_not_local().map(make_key_http1); let mut req = http::Request::new(()); req.extensions_mut() @@ -156,9 +161,7 @@ mod tests { &None, )); - inbound.recognize(&req) == default.map(|addr| - (addr, bind::Protocol::Http1(Host::NoAuthority)) - ) + inbound.recognize(&req) == default.map(make_key_http1) } fn recognize_default_no_ctx(default: Option) -> bool { @@ -168,9 +171,7 @@ mod tests { let req = http::Request::new(()); - inbound.recognize(&req) == default.map(|addr| - (addr, bind::Protocol::Http1(Host::NoAuthority)) - ) + inbound.recognize(&req) == default.map(make_key_http1) } fn recognize_default_no_loop( @@ -191,9 +192,7 @@ mod tests { &Some(local), )); - inbound.recognize(&req) == default.map(|addr| - (addr, bind::Protocol::Http1(Host::NoAuthority)) - ) + inbound.recognize(&req) == default.map(make_key_http1) } } } diff --git a/proxy/src/transparency/client.rs b/proxy/src/transparency/client.rs index 8b1c04a31..f7b420d36 100644 --- a/proxy/src/transparency/client.rs +++ b/proxy/src/transparency/client.rs @@ -84,8 +84,12 @@ where -> Self { match *protocol { - bind::Protocol::Http1(_) => { + bind::Protocol::Http1 { .. } => { let h1 = hyper::Client::configure() + // TODO: when using the latest version of Hyper, we'll want + // to configure the connector with whether or not the + // request URI is in absolute form, since this will be + // handled at the connection level soon. .connector(HyperConnect::new(connect)) .body() // hyper should never try to automatically set the Host diff --git a/proxy/src/transparency/h1.rs b/proxy/src/transparency/h1.rs index f189e4ce0..3bc9a2b7b 100644 --- a/proxy/src/transparency/h1.rs +++ b/proxy/src/transparency/h1.rs @@ -17,8 +17,11 @@ pub struct UriIsAbsoluteForm; /// /// Also sets the `UriIsAbsoluteForm` extension if received `Uri` was /// already in absolute-form. -pub fn normalize_our_view_of_uri(req: &mut http::Request) { - if req.uri().authority_part().is_some() { +pub fn normalize_our_view_of_uri( + req: &mut http::Request, + was_absolute_form: bool +) { + if was_absolute_form { req.extensions_mut().insert(UriIsAbsoluteForm); return; }