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:
parent
be1b06fa22
commit
86a75907ca
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue