Skip to content

Commit

Permalink
proxy: Move absolute URI detection to bind::Protocol (linkerd#938)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
hawkw committed May 15, 2018
1 parent 12b520a commit b281938
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 27 deletions.
54 changes: 43 additions & 11 deletions proxy/src/bind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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>;
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -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 }
}
}

Expand All @@ -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))
}
}
}

Expand All @@ -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> {
Expand Down Expand Up @@ -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,
}
}
Expand Down
25 changes: 12 additions & 13 deletions proxy/src/inbound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ where
let key = key.map(move |addr| (addr, proto));
trace!("recognize key={:?}", key);


key
}

Expand Down Expand Up @@ -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,
Expand All @@ -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()
Expand All @@ -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 {
Expand All @@ -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(
Expand All @@ -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)
}
}
}
6 changes: 5 additions & 1 deletion proxy/src/transparency/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions proxy/src/transparency/h1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit b281938

Please sign in to comment.