Skip to content

Commit bf5cce0

Browse files
committed
feat(client): allow to set a specific sni hostname per request
1 parent cec4ef4 commit bf5cce0

5 files changed

Lines changed: 51 additions & 18 deletions

File tree

client/src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ impl Client {
328328

329329
let (conn, version) = self
330330
.connector
331-
.call((connect.hostname(), conn))
331+
.call((connect.sni_hostname(), conn))
332332
.timeout(timer.as_mut())
333333
.await
334334
.map_err(|_| TimeoutError::TlsHandshake)??;

client/src/connect.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use core::{fmt, iter, net::SocketAddr};
22

3-
use std::collections::vec_deque::{self, VecDeque};
4-
3+
use crate::request::SniHostname;
54
use crate::uri::Uri;
5+
use std::collections::vec_deque::{self, VecDeque};
66

77
pub trait Address {
88
/// Get hostname part.
@@ -80,17 +80,19 @@ pub struct Connect<'a> {
8080
pub(crate) uri: Uri<'a>,
8181
pub(crate) port: u16,
8282
pub(crate) addr: Addrs,
83+
pub(crate) sni_hostname: Option<&'a SniHostname>,
8384
}
8485

8586
impl<'a> Connect<'a> {
8687
/// Create `Connect` instance by splitting the string by ':' and convert the second part to u16
87-
pub fn new(uri: Uri<'a>) -> Self {
88+
pub fn new(uri: Uri<'a>, sni_hostname: Option<&'a SniHostname>) -> Self {
8889
let (_, port) = parse_host(uri.hostname());
8990

9091
Self {
9192
uri,
9293
port: port.unwrap_or(0),
9394
addr: Addrs::None,
95+
sni_hostname,
9496
}
9597
}
9698

@@ -112,6 +114,13 @@ impl<'a> Connect<'a> {
112114
self.uri.hostname()
113115
}
114116

117+
/// Get sni hostname.
118+
pub fn sni_hostname(&self) -> &str {
119+
self.sni_hostname
120+
.map(|s| s.0.as_str())
121+
.unwrap_or_else(|| self.hostname())
122+
}
123+
115124
/// Get request port.
116125
pub fn port(&self) -> u16 {
117126
Address::port(&self.uri).unwrap_or(self.port)

client/src/connection.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use core::hash::{Hash, Hasher};
22

3+
use super::{connect::Connect, tls::TlsStream, uri::Uri};
4+
use crate::request::SniHostname;
35
use xitca_http::http::uri::{Authority, PathAndQuery};
4-
5-
use super::{tls::TlsStream, uri::Uri};
6+
use xitca_unsafe_collection::bytes::BytesStr;
67

78
/// exclusive connection for http1 and in certain case they can be upgraded to [ConnectionShared]
89
pub type ConnectionExclusive = TlsStream;
@@ -34,10 +35,17 @@ impl From<crate::h3::Connection> for ConnectionShared {
3435
#[doc(hidden)]
3536
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
3637
pub enum ConnectionKey {
37-
Regular(Authority),
38+
Regular(AuthorityWithSni),
3839
Unix(AuthorityWithPath),
3940
}
4041

42+
#[doc(hidden)]
43+
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
44+
pub struct AuthorityWithSni {
45+
authority: Authority,
46+
sni: Option<SniHostname>,
47+
}
48+
4149
#[doc(hidden)]
4250
#[derive(Eq, Debug, Clone)]
4351
pub struct AuthorityWithPath {
@@ -58,10 +66,13 @@ impl Hash for AuthorityWithPath {
5866
}
5967
}
6068

61-
impl From<&Uri<'_>> for ConnectionKey {
62-
fn from(uri: &Uri<'_>) -> Self {
63-
match *uri {
64-
Uri::Tcp(uri) | Uri::Tls(uri) => ConnectionKey::Regular(uri.authority().unwrap().clone()),
69+
impl From<&Connect<'_>> for ConnectionKey {
70+
fn from(connect: &Connect<'_>) -> Self {
71+
match connect.uri {
72+
Uri::Tcp(uri) | Uri::Tls(uri) => ConnectionKey::Regular(AuthorityWithSni {
73+
authority: uri.authority().unwrap().clone(),
74+
sni: connect.sni_hostname.cloned(),
75+
}),
6576
Uri::Unix(uri) => ConnectionKey::Unix(AuthorityWithPath {
6677
authority: uri.authority().unwrap().clone(),
6778
path_and_query: uri.path_and_query().unwrap().clone(),

client/src/request.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use core::{marker::PhantomData, time::Duration};
22

3-
use futures_core::Stream;
4-
53
use crate::{
64
body::{BodyError, BoxBody, Once},
75
bytes::Bytes,
@@ -15,6 +13,8 @@ use crate::{
1513
response::Response,
1614
service::ServiceRequest,
1715
};
16+
use futures_core::Stream;
17+
use xitca_unsafe_collection::bytes::BytesStr;
1818

1919
/// builder type for [http::Request] with extended functionalities.
2020
pub struct RequestBuilder<'a, M = marker::Http> {
@@ -210,6 +210,15 @@ impl<'a, M> RequestBuilder<'a, M> {
210210
self
211211
}
212212

213+
/// Set SNI hostname of this request.
214+
#[inline]
215+
pub fn sni_hostname(mut self, sni_hostname: &str) -> Self {
216+
self.req
217+
.extensions_mut()
218+
.insert(SniHostname(BytesStr::from(sni_hostname)));
219+
self
220+
}
221+
213222
fn map_body<B, E>(mut self, b: B) -> RequestBuilder<'a, M>
214223
where
215224
B: Stream<Item = Result<Bytes, E>> + Send + 'static,
@@ -219,3 +228,6 @@ impl<'a, M> RequestBuilder<'a, M> {
219228
self
220229
}
221230
}
231+
232+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
233+
pub struct SniHostname(pub(crate) BytesStr);

client/src/service.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,14 @@ pub(crate) fn base_service() -> HttpService {
9494
#[allow(unused_mut)]
9595
let mut version = req.version();
9696

97-
let mut connect = Connect::new(uri);
97+
let sni_hostname = req.extensions().get();
98+
let mut connect = Connect::new(uri, sni_hostname);
9899

99100
let _date = client.date_service.handle();
100101

101102
loop {
102103
match version {
103-
Version::HTTP_2 | Version::HTTP_3 => match client.shared_pool.acquire(&connect.uri).await {
104+
Version::HTTP_2 | Version::HTTP_3 => match client.shared_pool.acquire(&connect).await {
104105
shared::AcquireOutput::Conn(mut _conn) => {
105106
let mut _timer = Box::pin(tokio::time::sleep(timeout));
106107
*req.version_mut() = version;
@@ -155,7 +156,7 @@ pub(crate) fn base_service() -> HttpService {
155156
if let Ok(Ok(conn)) = crate::h3::proto::connect(
156157
&client.h3_client,
157158
connect.addrs(),
158-
connect.hostname(),
159+
connect.sni_hostname(),
159160
)
160161
.timeout(timer.as_mut())
161162
.await
@@ -197,7 +198,7 @@ pub(crate) fn base_service() -> HttpService {
197198

198199
#[cfg(feature = "http1")]
199200
{
200-
client.exclusive_pool.try_add(&connect.uri, conn);
201+
client.exclusive_pool.try_add(&connect, conn);
201202
// downgrade request version to what alpn protocol suggested from make_exclusive.
202203
version = alpn_version;
203204
}
@@ -212,7 +213,7 @@ pub(crate) fn base_service() -> HttpService {
212213
_ => unreachable!("outer match didn't handle version correctly."),
213214
},
214215
},
215-
version => match client.exclusive_pool.acquire(&connect.uri).await {
216+
version => match client.exclusive_pool.acquire(&connect).await {
216217
exclusive::AcquireOutput::Conn(mut _conn) => {
217218
*req.version_mut() = version;
218219

0 commit comments

Comments
 (0)