Skip to content

Commit cd300ac

Browse files
committed
feat: stabilize Config
I've been using this in projects for a couple months now, and would like to land support in Surf directly, for which this should be stabilized. Refs: http-rs/surf#310 Refs: #86 Closes: #93
1 parent 772154e commit cd300ac

File tree

9 files changed

+48
-94
lines changed

9 files changed

+48
-94
lines changed

Diff for: Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ rustdoc-args = ["--cfg", "feature=\"docs\""]
2121

2222
[features]
2323
default = ["h1_client", "native-tls"]
24-
docs = ["h1_client", "curl_client", "wasm_client", "hyper_client", "unstable-config"]
24+
docs = ["h1_client", "curl_client", "wasm_client", "hyper_client"]
2525

2626
h1_client = ["async-h1", "async-std", "deadpool", "futures"]
2727
native_client = ["curl_client", "wasm_client"]
@@ -32,7 +32,7 @@ hyper_client = ["hyper", "hyper-tls", "http-types/hyperium_http", "futures-util"
3232
native-tls = ["async-native-tls"]
3333
rustls = ["async-tls", "rustls_crate"]
3434

35-
unstable-config = []
35+
unstable-config = [] # deprecated
3636

3737
[dependencies]
3838
async-trait = "0.1.37"

Diff for: src/config.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use std::fmt::Debug;
44
use std::time::Duration;
55

66
/// Configuration for `HttpClient`s.
7-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
87
#[non_exhaustive]
98
#[derive(Clone)]
109
pub struct Config {
@@ -24,6 +23,17 @@ pub struct Config {
2423
///
2524
/// Default: `Some(Duration::from_secs(60))`.
2625
pub timeout: Option<Duration>,
26+
/// Maximum number of simultaneous connections that this client is allowed to keep open to individual hosts at one time.
27+
///
28+
/// Default: `50`.
29+
/// This number is based on a few random benchmarks and see whatever gave decent perf vs resource use in Orogene.
30+
///
31+
/// Note: The behavior of this is different depending on the backend in use.
32+
/// - `h1_client`: `0` is disallowed and asserts as otherwise it would cause a semaphore deadlock.
33+
/// - `curl_client`: `0` allows for limitless connections per host.
34+
/// - `hyper_client`: No effect. Hyper does not support such an option.
35+
/// - `wasm_client`: No effect. Web browsers do not support such an option.
36+
pub max_connections_per_host: usize,
2737
/// TLS Configuration (Rustls)
2838
#[cfg_attr(feature = "docs", doc(cfg(feature = "h1_client")))]
2939
#[cfg(all(feature = "h1_client", feature = "rustls"))]
@@ -40,7 +50,8 @@ impl Debug for Config {
4050
dbg_struct
4151
.field("http_keep_alive", &self.http_keep_alive)
4252
.field("tcp_no_delay", &self.tcp_no_delay)
43-
.field("timeout", &self.timeout);
53+
.field("timeout", &self.timeout)
54+
.field("max_connections_per_host", &self.max_connections_per_host);
4455

4556
#[cfg(all(feature = "h1_client", feature = "rustls"))]
4657
{
@@ -66,6 +77,7 @@ impl Config {
6677
http_keep_alive: true,
6778
tcp_no_delay: false,
6879
timeout: Some(Duration::from_secs(60)),
80+
max_connections_per_host: 50,
6981
#[cfg(all(feature = "h1_client", any(feature = "rustls", feature = "native-tls")))]
7082
tls_config: None,
7183
}
@@ -97,6 +109,12 @@ impl Config {
97109
self
98110
}
99111

112+
/// Set the maximum number of simultaneous connections that this client is allowed to keep open to individual hosts at one time.
113+
pub fn max_connections_per_host(mut self, max_connections_per_host: usize) -> Self {
114+
self.max_connections_per_host = max_connections_per_host;
115+
self
116+
}
117+
100118
/// Set TLS Configuration (Rustls)
101119
#[cfg_attr(feature = "docs", doc(cfg(feature = "h1_client")))]
102120
#[cfg(all(feature = "h1_client", feature = "rustls"))]

Diff for: src/h1/mod.rs

+18-30
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
//! http-client implementation for async-h1, with connection pooling ("Keep-Alive").
22
3-
#[cfg(feature = "unstable-config")]
43
use std::convert::{Infallible, TryFrom};
5-
64
use std::fmt::Debug;
75
use std::net::SocketAddr;
86
use std::sync::Arc;
@@ -33,19 +31,15 @@ use tcp::{TcpConnWrapper, TcpConnection};
3331
#[cfg(any(feature = "native-tls", feature = "rustls"))]
3432
use tls::{TlsConnWrapper, TlsConnection};
3533

36-
// This number is based on a few random benchmarks and see whatever gave decent perf vs resource use.
37-
const DEFAULT_MAX_CONCURRENT_CONNECTIONS: usize = 50;
38-
3934
type HttpPool = DashMap<SocketAddr, Pool<TcpStream, std::io::Error>>;
4035
#[cfg(any(feature = "native-tls", feature = "rustls"))]
4136
type HttpsPool = DashMap<SocketAddr, Pool<TlsStream<TcpStream>, Error>>;
4237

43-
/// Async-h1 based HTTP Client, with connecton pooling ("Keep-Alive").
38+
/// async-h1 based HTTP Client, with connection pooling ("Keep-Alive").
4439
pub struct H1Client {
4540
http_pools: HttpPool,
4641
#[cfg(any(feature = "native-tls", feature = "rustls"))]
4742
https_pools: HttpsPool,
48-
max_concurrent_connections: usize,
4943
config: Arc<Config>,
5044
}
5145

@@ -82,10 +76,6 @@ impl Debug for H1Client {
8276
.collect::<Vec<String>>(),
8377
)
8478
.field("https_pools", &https_pools)
85-
.field(
86-
"max_concurrent_connections",
87-
&self.max_concurrent_connections,
88-
)
8979
.field("config", &self.config)
9080
.finish()
9181
}
@@ -104,19 +94,25 @@ impl H1Client {
10494
http_pools: DashMap::new(),
10595
#[cfg(any(feature = "native-tls", feature = "rustls"))]
10696
https_pools: DashMap::new(),
107-
max_concurrent_connections: DEFAULT_MAX_CONCURRENT_CONNECTIONS,
10897
config: Arc::new(Config::default()),
10998
}
11099
}
111100

112101
/// Create a new instance.
113102
pub fn with_max_connections(max: usize) -> Self {
103+
#[cfg(features = "h1_client")]
104+
assert!(max > 0, "max_connections_per_host with h1_client must be greater than zero or it will deadlock!");
105+
106+
let config = Config {
107+
max_connections_per_host: max,
108+
..Default::default()
109+
};
110+
114111
Self {
115112
http_pools: DashMap::new(),
116113
#[cfg(any(feature = "native-tls", feature = "rustls"))]
117114
https_pools: DashMap::new(),
118-
max_concurrent_connections: max,
119-
config: Arc::new(Config::default()),
115+
config: Arc::new(config),
120116
}
121117
}
122118
}
@@ -157,7 +153,6 @@ impl HttpClient for H1Client {
157153
for (idx, addr) in addrs.into_iter().enumerate() {
158154
let has_another_addr = idx != max_addrs_idx;
159155

160-
#[cfg(feature = "unstable-config")]
161156
if !self.config.http_keep_alive {
162157
match scheme {
163158
"http" => {
@@ -196,7 +191,7 @@ impl HttpClient for H1Client {
196191
let manager = TcpConnection::new(addr, self.config.clone());
197192
let pool = Pool::<TcpStream, std::io::Error>::new(
198193
manager,
199-
self.max_concurrent_connections,
194+
self.config.max_connections_per_host,
200195
);
201196
self.http_pools.insert(addr, pool);
202197
self.http_pools.get(&addr).unwrap()
@@ -216,14 +211,11 @@ impl HttpClient for H1Client {
216211
req.set_local_addr(stream.local_addr().ok());
217212

218213
let tcp_conn = client::connect(TcpConnWrapper::new(stream), req);
219-
#[cfg(feature = "unstable-config")]
220214
return if let Some(timeout) = self.config.timeout {
221215
async_std::future::timeout(timeout, tcp_conn).await?
222216
} else {
223217
tcp_conn.await
224218
};
225-
#[cfg(not(feature = "unstable-config"))]
226-
return tcp_conn.await;
227219
}
228220
#[cfg(any(feature = "native-tls", feature = "rustls"))]
229221
"https" => {
@@ -233,7 +225,7 @@ impl HttpClient for H1Client {
233225
let manager = TlsConnection::new(host.clone(), addr, self.config.clone());
234226
let pool = Pool::<TlsStream<TcpStream>, Error>::new(
235227
manager,
236-
self.max_concurrent_connections,
228+
self.config.max_connections_per_host,
237229
);
238230
self.https_pools.insert(addr, pool);
239231
self.https_pools.get(&addr).unwrap()
@@ -253,14 +245,11 @@ impl HttpClient for H1Client {
253245
req.set_local_addr(stream.get_ref().local_addr().ok());
254246

255247
let tls_conn = client::connect(TlsConnWrapper::new(stream), req);
256-
#[cfg(feature = "unstable-config")]
257248
return if let Some(timeout) = self.config.timeout {
258249
async_std::future::timeout(timeout, tls_conn).await?
259250
} else {
260251
tls_conn.await
261252
};
262-
#[cfg(not(feature = "unstable-config"))]
263-
return tls_conn.await;
264253
}
265254
_ => unreachable!(),
266255
}
@@ -272,36 +261,35 @@ impl HttpClient for H1Client {
272261
))
273262
}
274263

275-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
276-
#[cfg(feature = "unstable-config")]
277264
/// Override the existing configuration with new configuration.
278265
///
279266
/// Config options may not impact existing connections.
280267
fn set_config(&mut self, config: Config) -> http_types::Result<()> {
268+
#[cfg(features = "h1_client")]
269+
assert!(config.max_connections_per_host > 0, "max_connections_per_host with h1_client must be greater than zero or it will deadlock!");
270+
281271
self.config = Arc::new(config);
282272

283273
Ok(())
284274
}
285275

286-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
287-
#[cfg(feature = "unstable-config")]
288276
/// Get the current configuration.
289277
fn config(&self) -> &Config {
290278
&*self.config
291279
}
292280
}
293281

294-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
295-
#[cfg(feature = "unstable-config")]
296282
impl TryFrom<Config> for H1Client {
297283
type Error = Infallible;
298284

299285
fn try_from(config: Config) -> Result<Self, Self::Error> {
286+
#[cfg(features = "h1_client")]
287+
assert!(config.max_connections_per_host > 0, "max_connections_per_host with h1_client must be greater than zero or it will deadlock!");
288+
300289
Ok(Self {
301290
http_pools: DashMap::new(),
302291
#[cfg(any(feature = "native-tls", feature = "rustls"))]
303292
https_pools: DashMap::new(),
304-
max_concurrent_connections: DEFAULT_MAX_CONCURRENT_CONNECTIONS,
305293
config: Arc::new(config),
306294
})
307295
}

Diff for: src/h1/tcp.rs

-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ impl Manager<TcpStream, std::io::Error> for TcpConnection {
6565
async fn create(&self) -> Result<TcpStream, std::io::Error> {
6666
let tcp_stream = TcpStream::connect(self.addr).await?;
6767

68-
#[cfg(feature = "unstable-config")]
6968
tcp_stream.set_nodelay(self.config.tcp_no_delay)?;
7069

7170
Ok(tcp_stream)
@@ -75,7 +74,6 @@ impl Manager<TcpStream, std::io::Error> for TcpConnection {
7574
let mut buf = [0; 4];
7675
let mut cx = Context::from_waker(futures::task::noop_waker_ref());
7776

78-
#[cfg(feature = "unstable-config")]
7977
conn.set_nodelay(self.config.tcp_no_delay)?;
8078

8179
match Pin::new(conn).poll_read(&mut cx, &mut buf) {

Diff for: src/h1/tls.rs

-8
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ impl Manager<TlsStream<TcpStream>, Error> for TlsConnection {
7474
async fn create(&self) -> Result<TlsStream<TcpStream>, Error> {
7575
let raw_stream = async_std::net::TcpStream::connect(self.addr).await?;
7676

77-
#[cfg(feature = "unstable-config")]
7877
raw_stream.set_nodelay(self.config.tcp_no_delay)?;
7978

8079
let tls_stream = add_tls(&self.host, raw_stream, &self.config).await?;
@@ -85,7 +84,6 @@ impl Manager<TlsStream<TcpStream>, Error> for TlsConnection {
8584
let mut buf = [0; 4];
8685
let mut cx = Context::from_waker(futures::task::noop_waker_ref());
8786

88-
#[cfg(feature = "unstable-config")]
8987
conn.get_ref()
9088
.set_nodelay(self.config.tcp_no_delay)
9189
.map_err(Error::from)?;
@@ -108,14 +106,11 @@ cfg_if::cfg_if! {
108106
if #[cfg(feature = "rustls")] {
109107
#[allow(unused_variables)]
110108
pub(crate) async fn add_tls(host: &str, stream: TcpStream, config: &Config) -> Result<TlsStream<TcpStream>, std::io::Error> {
111-
#[cfg(all(feature = "h1_client", feature = "unstable-config"))]
112109
let connector = if let Some(tls_config) = config.tls_config.as_ref().cloned() {
113110
tls_config.into()
114111
} else {
115112
async_tls::TlsConnector::default()
116113
};
117-
#[cfg(not(feature = "unstable-config"))]
118-
let connector = async_tls::TlsConnector::default();
119114

120115
connector.connect(host, stream).await
121116
}
@@ -126,10 +121,7 @@ cfg_if::cfg_if! {
126121
stream: TcpStream,
127122
config: &Config,
128123
) -> Result<TlsStream<TcpStream>, async_native_tls::Error> {
129-
#[cfg(feature = "unstable-config")]
130124
let connector = config.tls_config.as_ref().cloned().unwrap_or_default();
131-
#[cfg(not(feature = "unstable-config"))]
132-
let connector = async_native_tls::TlsConnector::new();
133125

134126
connector.connect(host, stream).await
135127
}

Diff for: src/hyper.rs

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
//! http-client implementation for reqwest
22
3-
#[cfg(feature = "unstable-config")]
4-
use std::convert::Infallible;
5-
use std::convert::TryFrom;
3+
use std::convert::{Infallible, TryFrom};
64
use std::fmt::Debug;
75
use std::io;
86
use std::str::FromStr;
@@ -74,7 +72,6 @@ impl HttpClient for HyperClient {
7472
let req = HyperHttpRequest::try_from(req).await?.into_inner();
7573

7674
let conn_fut = self.client.dyn_request(req);
77-
#[cfg(feature = "unstable-config")]
7875
let response = if let Some(timeout) = self.config.timeout {
7976
match tokio::time::timeout(timeout, conn_fut).await {
8077
Err(_elapsed) => Err(Error::from_str(400, "Client timed out")),
@@ -85,15 +82,10 @@ impl HttpClient for HyperClient {
8582
conn_fut.await?
8683
};
8784

88-
#[cfg(not(feature = "unstable-config"))]
89-
let response = conn_fut.await?;
90-
9185
let res = HttpTypesResponse::try_from(response).await?.into_inner();
9286
Ok(res)
9387
}
9488

95-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
96-
#[cfg(feature = "unstable-config")]
9789
/// Override the existing configuration with new configuration.
9890
///
9991
/// Config options may not impact existing connections.
@@ -111,16 +103,12 @@ impl HttpClient for HyperClient {
111103
Ok(())
112104
}
113105

114-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
115-
#[cfg(feature = "unstable-config")]
116106
/// Get the current configuration.
117107
fn config(&self) -> &Config {
118108
&self.config
119109
}
120110
}
121111

122-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
123-
#[cfg(feature = "unstable-config")]
124112
impl TryFrom<Config> for HyperClient {
125113
type Error = Infallible;
126114

Diff for: src/isahc.rs

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
//! http-client implementation for isahc
22
3-
#[cfg(feature = "unstable-config")]
43
use std::convert::TryFrom;
54

65
use async_std::io::BufReader;
7-
#[cfg(feature = "unstable-config")]
86
use isahc::config::Configurable;
97
use isahc::{http, ResponseExt};
108

@@ -75,13 +73,12 @@ impl HttpClient for IsahcClient {
7573
Ok(response)
7674
}
7775

78-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
79-
#[cfg(feature = "unstable-config")]
8076
/// Override the existing configuration with new configuration.
8177
///
8278
/// Config options may not impact existing connections.
8379
fn set_config(&mut self, config: Config) -> http_types::Result<()> {
84-
let mut builder = isahc::HttpClient::builder();
80+
let mut builder =
81+
isahc::HttpClient::builder().max_connections_per_host(config.max_connections_per_host);
8582

8683
if !config.http_keep_alive {
8784
builder = builder.connection_cache_size(0);
@@ -99,16 +96,12 @@ impl HttpClient for IsahcClient {
9996
Ok(())
10097
}
10198

102-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
103-
#[cfg(feature = "unstable-config")]
10499
/// Get the current configuration.
105100
fn config(&self) -> &Config {
106101
&self.config
107102
}
108103
}
109104

110-
#[cfg_attr(feature = "docs", doc(cfg(feature = "unstable-config")))]
111-
#[cfg(feature = "unstable-config")]
112105
impl TryFrom<Config> for IsahcClient {
113106
type Error = isahc::Error;
114107

0 commit comments

Comments
 (0)