use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
use crate::pool::ConnectionPool;
use crate::runtime::{ConnectorSend, Resolve, RuntimePoll};
#[cfg(feature = "rustls")]
use crate::tls::TlsVersion;
use super::builder::HttpEngineBuilder;
use super::{HttpEngineCore, HttpEngineSend};
impl<R: RuntimePoll, C: ConnectorSend> HttpEngineBuilder<R, C> {
pub fn message_signature_async(
mut self,
config: crate::message_signatures::MessageSignatureConfig,
signer: impl crate::message_signatures::MessageSignatureAsyncSigner,
) -> Self {
self.message_signature = Some(
crate::message_signatures::AutomaticMessageSignature::new_async_send(
config,
Arc::new(signer),
),
);
self
}
#[cfg(feature = "tower")]
pub fn connector_layer<L>(mut self, layer: L) -> Self
where
L: tower_layer::Layer<crate::connector::ConnectorServiceSend<C>>,
L::Service: tower_service::Service<
crate::connector::ConnectInfo,
Response = C::Stream,
Error = std::io::Error,
> + Send
+ Sync
+ Clone
+ 'static,
<L::Service as tower_service::Service<crate::connector::ConnectInfo>>::Future:
Send + 'static,
{
self.tower_connector = Some(crate::connector::TowerConnectorSendSlot::new(
crate::connector::apply_layer_send(self.connector.clone(), layer),
));
self
}
#[cfg(all(feature = "http3", feature = "rustls"))]
pub fn http3(mut self, enable: bool) -> Result<Self, crate::error::Error> {
if enable {
self = self.ensure_h3_endpoint()?;
self.prefer_h3 = true;
} else {
self.h3_endpoint = None;
self.prefer_h3 = false;
}
Ok(self)
}
#[cfg(all(feature = "http3", feature = "rustls"))]
pub fn alt_svc_h3(mut self, enable: bool) -> Result<Self, crate::error::Error> {
if enable {
self = self.ensure_h3_endpoint()?;
} else if !self.prefer_h3 {
self.h3_endpoint = None;
}
Ok(self)
}
#[cfg(all(feature = "http3", feature = "rustls"))]
pub fn h3_zero_rtt(mut self, enable: bool) -> Self {
self.h3_zero_rtt = enable;
self
}
#[cfg(all(feature = "http3", feature = "rustls"))]
fn ensure_h3_endpoint(mut self) -> Result<Self, crate::error::Error> {
if self.h3_endpoint.is_none() {
let tls_config = self
.tls
.as_ref()
.ok_or_else(|| {
crate::error::Error::Other(
"HTTP/3 requires a TLS connector — call .tls() before .http3(true)".into(),
)
})?
.config()
.clone();
let endpoint = crate::h3_transport::build_quinn_endpoint(
tls_config,
self.local_address,
self.h3_zero_rtt,
)
.map_err(|e| crate::error::Error::Other(Box::new(e)))?;
self.h3_endpoint = Some(endpoint);
}
Ok(self)
}
#[allow(unreachable_code)]
fn default_resolver() -> Option<Arc<dyn crate::runtime::Resolve>> {
#[cfg(feature = "tokio")]
{
return Some(Arc::new(crate::runtime::tokio_rt::DefaultResolver));
}
#[cfg(feature = "smol")]
{
return Some(Arc::new(crate::runtime::smol_rt::DefaultResolver));
}
#[cfg(feature = "compio")]
{
return Some(Arc::new(crate::runtime::compio_rt::DefaultResolver));
}
None
}
pub fn build(self) -> Result<HttpEngineSend<R, C>, crate::error::Error> {
let mut this = self;
if let Some(error) = this.builder_error.take() {
return Err(error.into_error());
}
let self_ = this;
let pool = if self_.no_connection_reuse {
ConnectionPool::new()
.with_max_idle_per_host(0)
.with_idle_timeout(Duration::ZERO)
} else {
let mut pool = ConnectionPool::new()
.with_max_idle_per_host(self_.pool_max_idle_per_host)
.with_max_active_per_host(self_.pool_max_active_per_host)
.with_idle_timeout(self_.pool_idle_timeout);
if let Some(max_active) = self_.pool_max_active_streams_per_connection {
pool = pool.with_max_active_streams_per_connection(max_active);
}
if let Some(max_lifetime) = self_.pool_max_lifetime {
pool.with_max_lifetime(max_lifetime)
} else {
pool
}
};
#[cfg(feature = "rustls")]
let tls = {
let has_version_constraints =
self_.min_tls_version.is_some() || self_.max_tls_version.is_some();
let has_extra_config =
!self_.extra_root_certs.is_empty() || self_.client_identity.is_some();
let has_crls = !self_.crls.is_empty();
let needs_configured = has_crls || self_.danger_accept_invalid_hostnames;
let needs_sni_update = self_.tls_sni == Some(false);
let mut connector = if self_.tls.is_some()
&& !has_version_constraints
&& !has_extra_config
&& !needs_configured
{
self_.tls
} else if needs_configured || has_extra_config || has_version_constraints {
let versions: Vec<&'static rustls::SupportedProtocolVersion> =
if has_version_constraints {
TlsVersion::filter_versions(self_.min_tls_version, self_.max_tls_version)?
} else {
vec![&rustls::version::TLS12, &rustls::version::TLS13]
};
if needs_configured {
let mut root_store = rustls::RootCertStore::from_iter(
webpki_roots::TLS_SERVER_ROOTS.iter().cloned(),
);
for cert in &self_.extra_root_certs {
#[allow(clippy::expect_used)]
root_store
.add(cert.der.clone())
.expect("invalid extra root certificate");
}
let crls: Vec<_> = self_.crls.into_iter().map(|c| c.der).collect();
let identity = self_.client_identity.map(|id| (id.certs, id.key));
Some(Arc::new(
#[allow(clippy::expect_used)]
crate::tls::RustlsConnector::build_configured(
root_store,
&versions,
crls,
self_.danger_accept_invalid_hostnames,
identity,
)
.expect(
"failed to build TLS configuration — check CRLs and client identity",
),
))
} else if let Some(identity) = self_.client_identity {
Some(Arc::new(
#[allow(clippy::expect_used)]
crate::tls::RustlsConnector::with_identity_versioned(
&self_.extra_root_certs,
identity,
&versions,
)
.expect("failed to build TLS configuration — check client identity (cert/key pair)"),
))
} else if !self_.extra_root_certs.is_empty() {
Some(Arc::new(
crate::tls::RustlsConnector::with_extra_roots_versioned(
&self_.extra_root_certs,
&versions,
),
))
} else {
Some(Arc::new(
crate::tls::RustlsConnector::with_webpki_roots_versioned(&versions),
))
}
} else {
Some(Arc::new(crate::tls::RustlsConnector::with_webpki_roots()))
};
if needs_sni_update {
let c = connector.get_or_insert_with(|| {
Arc::new(crate::tls::RustlsConnector::with_webpki_roots())
});
Arc::make_mut(c).config_mut().enable_sni = false;
}
connector
};
Ok(HttpEngineSend {
core: HttpEngineCore {
pool,
base_url: self_.base_url,
address_family: self_.address_family,
redirect_policy: self_.redirect_policy,
timeout: self_.timeout,
connect_timeout: self_.connect_timeout,
read_timeout: self_.read_timeout,
write_timeout: self_.write_timeout,
tcp_keepalive: self_.tcp_keepalive,
tcp_keepalive_interval: self_.tcp_keepalive_interval,
tcp_keepalive_retries: self_.tcp_keepalive_retries,
local_address: self_.local_address,
#[cfg(target_os = "linux")]
interface: self_.interface,
#[cfg(unix)]
unix_socket: self_.unix_socket,
https_only: self_.https_only,
referer: self_.referer,
no_connection_reuse: self_.no_connection_reuse,
tcp_fast_open: self_.tcp_fast_open,
accept_encoding_header: self_.accept_encoding.header_value(),
accept_encoding: self_.accept_encoding,
default_headers: Arc::new(self_.default_headers),
retry: self_.retry,
cookie_jar: self_.cookie_jar,
proxy: self_.proxy,
proxy_chain: self_.proxy_chain,
resolver: {
if let Some(overrides) = self_.static_resolves {
let fallback = self_.resolver.or_else(|| Self::default_resolver());
let mut sr = crate::runtime::StaticResolver::new(fallback);
for (host, addrs) in overrides {
sr.add(host, addrs);
}
Some(Arc::new(sr) as Arc<dyn Resolve>)
} else {
self_.resolver.or_else(|| Self::default_resolver())
}
},
http2: self_.http2,
middleware: self_.middleware,
rate_limiter: self_.rate_limiter,
bandwidth_limiter: self_.bandwidth_limiter,
digest_auth: self_.digest_auth,
message_signature: self_.message_signature,
automatic_content_digest: self_.automatic_content_digest,
cache: self_.cache,
hsts: self_.hsts,
h2c_probe_cache: self_
.h2c_probe_ttl
.map(crate::h2c_probe::H2cProbeCache::with_ttl)
.unwrap_or_else(crate::h2c_probe::H2cProbeCache::new),
connection_coalescing: self_.connection_coalescing,
sensitive_headers: self_.sensitive_headers,
observer: self_.observer,
#[cfg(feature = "rustls")]
tls,
#[cfg(all(feature = "http3", feature = "rustls"))]
h3_endpoint: self_.h3_endpoint,
#[cfg(all(feature = "http3", feature = "rustls"))]
prefer_h3: self_.prefer_h3,
#[cfg(all(feature = "http3", feature = "rustls"))]
h3_zero_rtt: self_.h3_zero_rtt,
#[cfg(all(feature = "http3", feature = "rustls"))]
alt_svc_cache: crate::alt_svc::AltSvcCache::new(),
},
connector: self_.connector,
#[cfg(feature = "tower")]
tower_connector: self_.tower_connector,
_phantom: PhantomData,
})
}
}