use http::{HeaderMap, HeaderValue, Uri};
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor;
use oxihttp_core::OxiHttpError;
use std::sync::Arc;
use std::time::Duration;
use crate::middleware::ClientMiddleware;
use crate::proxy::{ProxyConnector, ProxyKind};
use crate::redirect::RedirectPolicy;
use crate::resolver::BoxResolver;
use crate::retry::RetryPolicy;
#[cfg(feature = "socks")]
use crate::proxy::Socks5Connector;
#[cfg(feature = "tls")]
use crate::connector::OxiHttpsConnector;
#[cfg(feature = "tls")]
use crate::tls;
use super::Client;
#[cfg(feature = "tls")]
use super::TlsRebuildConfig;
#[derive(Debug, Clone, Default)]
pub struct Http2Settings {
pub initial_stream_window_size: Option<u32>,
pub initial_connection_window_size: Option<u32>,
pub adaptive_window: Option<bool>,
pub keep_alive_interval: Option<std::time::Duration>,
pub keep_alive_timeout: Option<std::time::Duration>,
pub max_frame_size: Option<u32>,
pub max_concurrent_reset_streams: Option<usize>,
pub max_send_buf_size: Option<usize>,
}
pub(crate) fn apply_http2_settings(
builder: &mut hyper_util::client::legacy::Builder,
settings: &Http2Settings,
) {
if let Some(sz) = settings.initial_stream_window_size {
builder.http2_initial_stream_window_size(sz);
}
if let Some(sz) = settings.initial_connection_window_size {
builder.http2_initial_connection_window_size(sz);
}
if let Some(adaptive) = settings.adaptive_window {
builder.http2_adaptive_window(adaptive);
}
if let Some(interval) = settings.keep_alive_interval {
builder.http2_keep_alive_interval(interval);
}
if let Some(timeout) = settings.keep_alive_timeout {
builder.http2_keep_alive_timeout(timeout);
}
if let Some(sz) = settings.max_frame_size {
builder.http2_max_frame_size(sz);
}
if let Some(n) = settings.max_concurrent_reset_streams {
builder.http2_max_concurrent_reset_streams(n);
}
if let Some(sz) = settings.max_send_buf_size {
builder.http2_max_send_buf_size(sz);
}
}
pub struct ClientBuilder {
pub(super) pool_max_idle_per_host: Option<usize>,
pub(super) pool_idle_timeout: Option<Duration>,
pub(super) connect_timeout: Option<Duration>,
pub(super) read_timeout: Option<Duration>,
pub(super) redirect_policy: RedirectPolicy,
pub(super) retry_policy: Option<RetryPolicy>,
pub(super) default_headers: HeaderMap,
pub(super) user_agent: Option<String>,
pub(super) decompression: bool,
pub(super) middleware: Vec<Arc<dyn ClientMiddleware>>,
pub(super) proxy: Option<ProxyKind>,
pub(super) cookie_jar: Option<Arc<std::sync::Mutex<oxihttp_core::CookieJar>>>,
pub(super) http2_settings: Option<Http2Settings>,
pub(super) tcp_nodelay: Option<bool>,
pub(super) tcp_keepalive: Option<Duration>,
pub(super) resolver: Option<Arc<dyn crate::resolver::DnsResolver>>,
#[cfg(feature = "tls")]
pub(super) trusted_certs_der: Vec<Vec<u8>>,
#[cfg(feature = "tls")]
pub(super) alpn: Vec<String>,
#[cfg(feature = "tls")]
pub(super) accept_invalid_certs: bool,
#[cfg(feature = "tls")]
pub(super) use_webpki_roots: bool,
#[cfg(feature = "tls")]
pub(super) key_log_path: Option<std::path::PathBuf>,
#[cfg(feature = "tls")]
pub(super) early_data: bool,
}
impl ClientBuilder {
pub fn new() -> Self {
Self {
pool_max_idle_per_host: None,
pool_idle_timeout: None,
connect_timeout: None,
read_timeout: None,
redirect_policy: RedirectPolicy::default(),
retry_policy: None,
default_headers: HeaderMap::new(),
user_agent: None,
decompression: false,
middleware: Vec::new(),
proxy: None,
cookie_jar: None,
http2_settings: None,
tcp_nodelay: None,
tcp_keepalive: None,
resolver: None,
#[cfg(feature = "tls")]
trusted_certs_der: Vec::new(),
#[cfg(feature = "tls")]
alpn: Vec::new(),
#[cfg(feature = "tls")]
accept_invalid_certs: false,
#[cfg(feature = "tls")]
use_webpki_roots: false,
#[cfg(feature = "tls")]
key_log_path: None,
#[cfg(feature = "tls")]
early_data: false,
}
}
pub fn pool_max_idle_per_host(mut self, n: usize) -> Self {
self.pool_max_idle_per_host = Some(n);
self
}
pub fn pool_idle_timeout(mut self, duration: Duration) -> Self {
self.pool_idle_timeout = Some(duration);
self
}
pub fn connect_timeout(mut self, duration: Duration) -> Self {
self.connect_timeout = Some(duration);
self
}
pub fn read_timeout(mut self, duration: Duration) -> Self {
self.read_timeout = Some(duration);
self
}
pub fn redirect_policy(mut self, policy: RedirectPolicy) -> Self {
self.redirect_policy = policy;
self
}
pub fn retry_policy(mut self, policy: RetryPolicy) -> Self {
self.retry_policy = Some(policy);
self
}
pub fn default_headers(mut self, headers: HeaderMap) -> Self {
self.default_headers = headers;
self
}
pub fn user_agent(mut self, agent: impl Into<String>) -> Self {
self.user_agent = Some(agent.into());
self
}
pub fn with_decompression(mut self, enabled: bool) -> Self {
self.decompression = enabled;
self
}
pub fn with_middleware<M: ClientMiddleware + 'static>(mut self, m: M) -> Self {
self.middleware.push(Arc::new(m));
self
}
pub fn with_layer<M: ClientMiddleware + 'static>(self, m: M) -> Self {
self.with_middleware(m)
}
pub fn with_cookie_jar(mut self, jar: Arc<std::sync::Mutex<oxihttp_core::CookieJar>>) -> Self {
self.cookie_jar = Some(jar);
self
}
pub fn with_new_cookie_jar(mut self) -> Self {
self.cookie_jar = Some(Arc::new(std::sync::Mutex::new(
oxihttp_core::CookieJar::new(),
)));
self
}
pub fn with_http2_settings(mut self, settings: Http2Settings) -> Self {
self.http2_settings = Some(settings);
self
}
pub fn with_tcp_nodelay(mut self, nodelay: bool) -> Self {
self.tcp_nodelay = Some(nodelay);
self
}
pub fn with_tcp_keepalive(mut self, duration: Duration) -> Self {
self.tcp_keepalive = Some(duration);
self
}
pub fn with_resolver<R: crate::resolver::DnsResolver>(mut self, r: R) -> Self {
self.resolver = Some(Arc::new(r));
self
}
pub fn with_http_proxy(mut self, uri: Uri) -> Self {
self.proxy = Some(ProxyKind::HttpConnect(uri));
self
}
#[cfg(feature = "socks")]
pub fn with_socks5_proxy(mut self, uri: Uri) -> Self {
self.proxy = Some(ProxyKind::Socks5(uri));
self
}
pub fn build_proxy(self) -> Result<Client<ProxyConnector>, OxiHttpError> {
let proxy_uri = match self.proxy.as_ref() {
Some(ProxyKind::HttpConnect(u)) => u.clone(),
#[cfg(feature = "socks")]
Some(ProxyKind::Socks5(_)) => {
return Err(OxiHttpError::ConnectionPool(
"SOCKS5 proxy configured; use build_socks5_proxy() instead".into(),
))
}
None => {
return Err(OxiHttpError::ConnectionPool(
"no proxy configured; call with_http_proxy() first".into(),
))
}
};
let connector = ProxyConnector::new(proxy_uri, self.connect_timeout);
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(dur) = self.pool_idle_timeout {
builder.pool_idle_timeout(dur);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let inner = builder.build(connector);
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
Ok(Client {
inner,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar.clone(),
#[cfg(feature = "tls")]
tls_rebuild: None,
})
}
#[cfg(feature = "socks")]
pub fn build_socks5_proxy(self) -> Result<Client<Socks5Connector>, OxiHttpError> {
let proxy_uri = match self.proxy.as_ref() {
Some(ProxyKind::Socks5(u)) => u.clone(),
Some(ProxyKind::HttpConnect(_)) => {
return Err(OxiHttpError::ConnectionPool(
"HTTP CONNECT proxy configured; use build_proxy() instead".into(),
))
}
None => {
return Err(OxiHttpError::ConnectionPool(
"no proxy configured; call with_socks5_proxy() first".into(),
))
}
};
let connector = Socks5Connector::new(proxy_uri, self.connect_timeout);
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(dur) = self.pool_idle_timeout {
builder.pool_idle_timeout(dur);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let inner = builder.build(connector);
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
Ok(Client {
inner,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar.clone(),
#[cfg(feature = "tls")]
tls_rebuild: None,
})
}
#[cfg(feature = "tls")]
pub fn build_proxy_https(
self,
) -> Result<Client<OxiHttpsConnector<ProxyConnector>>, OxiHttpError> {
let proxy_uri = match self.proxy.as_ref() {
Some(ProxyKind::HttpConnect(u)) => u.clone(),
#[cfg(feature = "socks")]
Some(ProxyKind::Socks5(_)) => {
return Err(OxiHttpError::ConnectionPool(
"SOCKS5 proxy configured; use build_socks5_proxy_https() instead".into(),
))
}
None => {
return Err(OxiHttpError::ConnectionPool(
"no proxy configured; call with_http_proxy() first".into(),
))
}
};
let tls_connector = tls::build_tls_connector(
&self.trusted_certs_der,
&self.alpn,
self.accept_invalid_certs,
self.use_webpki_roots,
self.key_log_path.clone(),
self.early_data,
)?;
let http_connector = ProxyConnector::new(proxy_uri, self.connect_timeout);
let https_connector = OxiHttpsConnector::new(http_connector, tls_connector);
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(dur) = self.pool_idle_timeout {
builder.pool_idle_timeout(dur);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let inner = builder.build(https_connector);
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
Ok(Client {
inner,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar.clone(),
#[cfg(feature = "tls")]
tls_rebuild: None,
})
}
#[cfg(all(feature = "tls", feature = "socks"))]
pub fn build_socks5_proxy_https(
self,
) -> Result<Client<OxiHttpsConnector<Socks5Connector>>, OxiHttpError> {
let proxy_uri = match self.proxy.as_ref() {
Some(ProxyKind::Socks5(u)) => u.clone(),
Some(ProxyKind::HttpConnect(_)) => {
return Err(OxiHttpError::ConnectionPool(
"HTTP CONNECT proxy configured; use build_proxy_https() instead".into(),
))
}
None => {
return Err(OxiHttpError::ConnectionPool(
"no proxy configured; call with_socks5_proxy() first".into(),
))
}
};
let tls_connector = tls::build_tls_connector(
&self.trusted_certs_der,
&self.alpn,
self.accept_invalid_certs,
self.use_webpki_roots,
self.key_log_path.clone(),
self.early_data,
)?;
let socks_connector = Socks5Connector::new(proxy_uri, self.connect_timeout);
let https_connector = OxiHttpsConnector::new(socks_connector, tls_connector);
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(dur) = self.pool_idle_timeout {
builder.pool_idle_timeout(dur);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let inner = builder.build(https_connector);
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
Ok(Client {
inner,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar.clone(),
#[cfg(feature = "tls")]
tls_rebuild: None,
})
}
#[cfg(feature = "tls")]
pub fn with_tls(mut self) -> Self {
self.use_webpki_roots = true;
self
}
#[cfg(feature = "tls")]
pub fn with_webpki_roots(mut self) -> Self {
self.use_webpki_roots = true;
self
}
#[cfg(feature = "tls")]
pub fn with_trusted_cert_der(mut self, der: Vec<u8>) -> Self {
self.trusted_certs_der.push(der);
self
}
#[cfg(feature = "tls")]
pub fn with_alpn(mut self, protocols: &[&str]) -> Self {
self.alpn = protocols.iter().map(|s| s.to_string()).collect();
self
}
#[cfg(feature = "tls")]
pub fn with_danger_accept_invalid_certs(mut self) -> Self {
self.accept_invalid_certs = true;
self
}
#[cfg(feature = "tls")]
pub fn with_key_log_file(mut self, path: std::path::PathBuf) -> Self {
self.key_log_path = Some(path);
self
}
#[cfg(feature = "tls")]
pub fn with_early_data(mut self) -> Self {
self.early_data = true;
self
}
pub fn build(self) -> Result<Client, OxiHttpError> {
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(dur) = self.pool_idle_timeout {
builder.pool_idle_timeout(dur);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let mut http = HttpConnector::new();
http.enforce_http(false);
if let Some(dur) = self.connect_timeout {
http.set_connect_timeout(Some(dur));
}
if let Some(nodelay) = self.tcp_nodelay {
http.set_nodelay(nodelay);
}
if let Some(ka) = self.tcp_keepalive {
http.set_keepalive(Some(ka));
}
let inner = builder.build(http);
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
Ok(Client {
inner,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar.clone(),
#[cfg(feature = "tls")]
tls_rebuild: None,
})
}
#[cfg(feature = "tls")]
pub fn build_https(self) -> Result<super::HttpsClient, OxiHttpError> {
let connector = tls::build_tls_connector(
&self.trusted_certs_der,
&self.alpn,
self.accept_invalid_certs,
self.use_webpki_roots,
self.key_log_path.clone(),
self.early_data,
)?;
let mut http = HttpConnector::new();
http.enforce_http(false);
if let Some(dur) = self.connect_timeout {
http.set_connect_timeout(Some(dur));
}
if let Some(nodelay) = self.tcp_nodelay {
http.set_nodelay(nodelay);
}
if let Some(ka) = self.tcp_keepalive {
http.set_keepalive(Some(ka));
}
let https_connector = OxiHttpsConnector::new(http, connector);
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(dur) = self.pool_idle_timeout {
builder.pool_idle_timeout(dur);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let inner = builder.build(https_connector);
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
let tls_rebuild = Arc::new(TlsRebuildConfig {
trusted_certs_der: self.trusted_certs_der.clone(),
alpn: self.alpn.clone(),
accept_invalid_certs: self.accept_invalid_certs,
use_webpki_roots: self.use_webpki_roots,
key_log_path: self.key_log_path.clone(),
early_data: self.early_data,
connect_timeout: self.connect_timeout,
tcp_nodelay: self.tcp_nodelay,
tcp_keepalive: self.tcp_keepalive,
http2_settings: self.http2_settings.clone(),
pool_max_idle_per_host: self.pool_max_idle_per_host,
pool_idle_timeout: self.pool_idle_timeout,
});
Ok(Client {
inner,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar.clone(),
tls_rebuild: Some(tls_rebuild),
})
}
pub fn build_with_resolver(self) -> Result<super::ResolverClient, OxiHttpError> {
let resolver = self.resolver.ok_or_else(|| {
OxiHttpError::Dns("with_resolver must be called before build_with_resolver".into())
})?;
let mut http = HttpConnector::new_with_resolver(BoxResolver(resolver));
http.enforce_http(false);
if let Some(dur) = self.connect_timeout {
http.set_connect_timeout(Some(dur));
}
if let Some(nodelay) = self.tcp_nodelay {
http.set_nodelay(nodelay);
}
if let Some(ka) = self.tcp_keepalive {
http.set_keepalive(Some(ka));
}
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(d) = self.pool_idle_timeout {
builder.pool_idle_timeout(d);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
Ok(Client {
inner: builder.build(http),
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar,
#[cfg(feature = "tls")]
tls_rebuild: None,
})
}
#[cfg(feature = "tls")]
pub fn build_https_with_resolver(self) -> Result<super::ResolverHttpsClient, OxiHttpError> {
let resolver = self.resolver.ok_or_else(|| {
OxiHttpError::Dns(
"with_resolver must be called before build_https_with_resolver".into(),
)
})?;
let tls_connector = tls::build_tls_connector(
&self.trusted_certs_der,
&self.alpn,
self.accept_invalid_certs,
self.use_webpki_roots,
self.key_log_path.clone(),
self.early_data,
)?;
let mut http = HttpConnector::new_with_resolver(BoxResolver(resolver));
http.enforce_http(false);
if let Some(dur) = self.connect_timeout {
http.set_connect_timeout(Some(dur));
}
if let Some(nodelay) = self.tcp_nodelay {
http.set_nodelay(nodelay);
}
if let Some(ka) = self.tcp_keepalive {
http.set_keepalive(Some(ka));
}
let connector = crate::connector::OxiHttpsConnector::new(http, tls_connector);
let mut builder = HyperClient::builder(TokioExecutor::new());
if let Some(n) = self.pool_max_idle_per_host {
builder.pool_max_idle_per_host(n);
}
if let Some(d) = self.pool_idle_timeout {
builder.pool_idle_timeout(d);
}
if let Some(ref h2) = self.http2_settings {
apply_http2_settings(&mut builder, h2);
}
let mut default_headers = self.default_headers;
if let Some(agent) = &self.user_agent {
if let Ok(val) = HeaderValue::from_str(agent) {
default_headers.insert(http::header::USER_AGENT, val);
}
}
Ok(Client {
inner: builder.build(connector),
connect_timeout: self.connect_timeout,
read_timeout: self.read_timeout,
redirect_policy: self.redirect_policy,
retry_policy: self.retry_policy,
default_headers,
decompression: self.decompression,
middleware: self.middleware,
cookie_jar: self.cookie_jar,
#[cfg(feature = "tls")]
tls_rebuild: None,
})
}
}
impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for ClientBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientBuilder")
.field("pool_max_idle_per_host", &self.pool_max_idle_per_host)
.field("pool_idle_timeout", &self.pool_idle_timeout)
.field("connect_timeout", &self.connect_timeout)
.field("read_timeout", &self.read_timeout)
.field("redirect_policy", &self.redirect_policy)
.field("retry_policy", &self.retry_policy)
.field("decompression", &self.decompression)
.field("user_agent", &self.user_agent)
.field("tcp_nodelay", &self.tcp_nodelay)
.field("tcp_keepalive", &self.tcp_keepalive)
.finish_non_exhaustive()
}
}