Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decompose hyper::Client into utils (part 1) #31

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
/// Legacy implementations of `connect` module and `Client`
#[cfg(feature = "client-legacy")]
pub mod legacy;

/// Client services
#[cfg(any(feature = "http1", feature = "http2"))]
pub mod services;
86 changes: 86 additions & 0 deletions src/client/services/http1_request_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use http::{uri::Scheme, Method, Request, Uri};
use hyper::service::Service;
use tracing::warn;

/// A `Service` that normalizes the request target.
pub struct Http1RequestTarget<S> {
inner: S,
is_proxied: bool,
}

impl<S> Http1RequestTarget<S> {
/// Create a new `Http1RequestTarget` service.
pub fn new(inner: S, is_proxied: bool) -> Self {
Self { inner, is_proxied }
}
}

impl<S, B> Service<Request<B>> for Http1RequestTarget<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;

fn call(&self, mut req: Request<B>) -> Self::Future {
// CONNECT always sends authority-form, so check it first...
if req.method() == Method::CONNECT {
authority_form(req.uri_mut());
} else if self.is_proxied {
absolute_form(req.uri_mut());
} else {
origin_form(req.uri_mut());
}
self.inner.call(req)
}
}

fn origin_form(uri: &mut Uri) {
let path = match uri.path_and_query() {
Some(path) if path.as_str() != "/" => {
let mut parts = ::http::uri::Parts::default();
parts.path_and_query = Some(path.clone());
Uri::from_parts(parts).expect("path is valid uri")
}
_none_or_just_slash => {
debug_assert!(Uri::default() == "/");
Uri::default()
}
};
*uri = path
}

fn absolute_form(uri: &mut Uri) {
debug_assert!(uri.scheme().is_some(), "absolute_form needs a scheme");
debug_assert!(
uri.authority().is_some(),
"absolute_form needs an authority"
);
// If the URI is to HTTPS, and the connector claimed to be a proxy,
// then it *should* have tunneled, and so we don't want to send
// absolute-form in that case.
if uri.scheme() == Some(&Scheme::HTTPS) {
origin_form(uri);
}
}

fn authority_form(uri: &mut Uri) {
if let Some(path) = uri.path_and_query() {
// `https://hyper.rs` would parse with `/` path, don't
// annoy people about that...
if path != "/" {
warn!("HTTP/1.1 CONNECT request stripping path: {:?}", path);
}
}
*uri = match uri.authority() {
Some(auth) => {
let mut parts = ::http::uri::Parts::default();
parts.authority = Some(auth.clone());
Uri::from_parts(parts).expect("authority is valid")
}
None => {
unreachable!("authority_form with relative uri");
}
};
}
5 changes: 5 additions & 0 deletions src/client/services/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod http1_request_target;
mod set_host;

pub use http1_request_target::Http1RequestTarget;
pub use set_host::SetHost;
52 changes: 52 additions & 0 deletions src/client/services/set_host.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use http::{header::HOST, uri::Port, HeaderValue, Request, Uri};
use hyper::service::Service;

/// A `Service` that sets the `Host` header, if it's missing, based on the request URI.
pub struct SetHost<S> {
inner: S,
}

impl<S> SetHost<S> {
/// Create a new `SetHost` service.
pub fn new(inner: S) -> Self {
Self { inner }
}
}

impl<S, B> Service<Request<B>> for SetHost<S>
where
S: Service<Request<B>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;

fn call(&self, mut req: Request<B>) -> Self::Future {
let uri = req.uri().clone();
req.headers_mut().entry(HOST).or_insert_with(|| {
let hostname = uri.host().expect("authority implies host");
if let Some(port) = get_non_default_port(&uri) {
let s = format!("{}:{}", hostname, port);
HeaderValue::from_str(&s)
} else {
HeaderValue::from_str(hostname)
}
.expect("uri host is valid header value")
});
self.inner.call(req)
}
}

fn get_non_default_port(uri: &Uri) -> Option<Port<&str>> {
match (uri.port().map(|p| p.as_u16()), is_schema_secure(uri)) {
(Some(443), true) => None,
(Some(80), false) => None,
_ => uri.port(),
}
}

fn is_schema_secure(uri: &Uri) -> bool {
uri.scheme_str()
.map(|scheme_str| matches!(scheme_str, "wss" | "https"))
.unwrap_or_default()
}