proxy: Move absolute URI detection to `bind::Protocol` (#938)

This is in preparation for landing the Tokio upgrade. 

In the upcoming Hyper release, the handling of absolute form request URIs
moved from `hyper::Request` to the `hyper::client::connect::Connect` trait.
Once we upgrade to the new Tokio, we will have to upgrade our Hyper 
dependency as well.

Currently, Conduit detects whether the request URI is in absolute form in
`h1::normalize_our_view_of_uri` and adds an extension to the request if it is.
This will no longer work with the new Hyper, as that function is called from
the `bind::NormalizeUri` service, which is not constructed until after the 
client connection is established. Therefore, it is necessary to move this
information to `bind::Protocol`, so that it can be passed to 
`transparency::client::HyperConnect` (our implementation of Hyper's `Connect`
trait) when we are using the newest Hyper. 

For now, however, I've left in the `UriIsAbsoluteForm` extension and continued
to set it in  `h1::normalize_our_view_of_uri`, since we currently still use it
on the current Hyper version. I thought it was good to minimize the changes to
this existing code, as it will be removed when we migrate to the new Hyper.

This PR shouldn't cause any functional changes.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
Eliza Weisman 2018-05-15 13:16:58 -07:00 committed by GitHub
parent be1b06fa22
commit 86a75907ca
4 changed files with 65 additions and 27 deletions

View File

@ -67,7 +67,16 @@ pub enum Binding<B: tower_h2::Body + 'static> {
/// 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<S> {
inner: S
inner: S,
was_absolute_form: bool,
}
pub type Service<B> = Binding<B>;
@ -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<S> NormalizeUri<S> {
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<S::Service>
>;
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<B: tower_h2::Body + 'static> tower::Service for Binding<B> {
@ -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,
}
}

View File

@ -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<net::SocketAddr>) -> 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)
}
}
}

View File

@ -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

View File

@ -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<B>(req: &mut http::Request<B>) {
if req.uri().authority_part().is_some() {
pub fn normalize_our_view_of_uri<B>(
req: &mut http::Request<B>,
was_absolute_form: bool
) {
if was_absolute_form {
req.extensions_mut().insert(UriIsAbsoluteForm);
return;
}