Skip to content

Commit 88af105

Browse files
committed
feat(client): add some general HTTP/1 client middleware
1 parent b9dc3d2 commit 88af105

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

src/client/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/// Legacy implementations of `connect` module and `Client`
44
#[cfg(feature = "client-legacy")]
55
pub mod legacy;
6+
pub mod service;
67

78
#[cfg(feature = "client-proxy")]
89
pub mod proxy;

src/client/service.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//! todo
2+
3+
use std::task::{Context, Poll};
4+
5+
use http::header::{HeaderValue, HOST};
6+
use http::{Method, Request, Uri};
7+
use tower_service::Service;
8+
9+
/// todo
10+
#[derive(Clone, Debug)]
11+
pub struct SetHost<S> {
12+
inner: S,
13+
}
14+
15+
/// todo
16+
#[derive(Clone, Debug)]
17+
pub struct Http1RequestTarget<S> {
18+
inner: S,
19+
}
20+
21+
// ===== impl SetHost =====
22+
23+
impl<S> SetHost<S> {
24+
/// todo
25+
pub fn new(inner: S) -> Self {
26+
SetHost { inner }
27+
}
28+
}
29+
30+
impl<S, ReqBody> Service<Request<ReqBody>> for SetHost<S>
31+
where
32+
S: Service<Request<ReqBody>>,
33+
{
34+
type Response = S::Response;
35+
type Error = S::Error;
36+
type Future = S::Future;
37+
38+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
39+
self.inner.poll_ready(cx)
40+
}
41+
42+
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
43+
if req.uri().authority().is_some() {
44+
let uri = req.uri().clone();
45+
req.headers_mut().entry(HOST).or_insert_with(|| {
46+
let hostname = uri.host().expect("authority implies host");
47+
if let Some(port) = get_non_default_port(&uri) {
48+
let s = format!("{hostname}:{port}");
49+
HeaderValue::from_str(&s)
50+
} else {
51+
HeaderValue::from_str(hostname)
52+
}
53+
.expect("uri host is valid header value")
54+
});
55+
}
56+
self.inner.call(req)
57+
}
58+
}
59+
60+
fn get_non_default_port(uri: &Uri) -> Option<http::uri::Port<&str>> {
61+
match (uri.port().map(|p| p.as_u16()), is_schema_secure(uri)) {
62+
(Some(443), true) => None,
63+
(Some(80), false) => None,
64+
_ => uri.port(),
65+
}
66+
}
67+
68+
fn is_schema_secure(uri: &Uri) -> bool {
69+
uri.scheme_str()
70+
.map(|scheme_str| matches!(scheme_str, "wss" | "https"))
71+
.unwrap_or_default()
72+
}
73+
74+
// ===== impl Http1RequestTarget =====
75+
76+
impl<S> Http1RequestTarget<S> {
77+
/// todo
78+
pub fn new(inner: S) -> Self {
79+
Http1RequestTarget { inner }
80+
}
81+
}
82+
83+
impl<S, ReqBody> Service<Request<ReqBody>> for Http1RequestTarget<S>
84+
where
85+
S: Service<Request<ReqBody>>,
86+
{
87+
type Response = S::Response;
88+
type Error = S::Error;
89+
type Future = S::Future;
90+
91+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
92+
self.inner.poll_ready(cx)
93+
}
94+
95+
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
96+
// CONNECT always sends authority-form, so check it first...
97+
if req.method() == Method::CONNECT {
98+
authority_form(req.uri_mut());
99+
} else {
100+
origin_form(req.uri_mut());
101+
}
102+
self.inner.call(req)
103+
}
104+
}
105+
106+
fn origin_form(uri: &mut Uri) {
107+
let path = match uri.path_and_query() {
108+
Some(path) if path.as_str() != "/" => {
109+
let mut parts = ::http::uri::Parts::default();
110+
parts.path_and_query = Some(path.clone());
111+
Uri::from_parts(parts).expect("path is valid uri")
112+
}
113+
_none_or_just_slash => {
114+
debug_assert!(Uri::default() == "/");
115+
Uri::default()
116+
}
117+
};
118+
*uri = path
119+
}
120+
121+
fn authority_form(uri: &mut Uri) {
122+
if let Some(path) = uri.path_and_query() {
123+
// `https://hyper.rs` would parse with `/` path, don't
124+
// annoy people about that...
125+
if path != "/" {
126+
tracing::debug!("HTTP/1.1 CONNECT request stripping path: {:?}", path);
127+
}
128+
}
129+
*uri = match uri.authority() {
130+
Some(auth) => {
131+
let mut parts = ::http::uri::Parts::default();
132+
parts.authority = Some(auth.clone());
133+
Uri::from_parts(parts).expect("authority is valid")
134+
}
135+
None => {
136+
unreachable!("authority_form with relative uri");
137+
}
138+
};
139+
}

0 commit comments

Comments
 (0)