#[cfg(any(feature = "rustls", feature = "boring"))]
use rama_core::layer::AddInputExtension;
use rama_core::rt::Executor;
use super::{
HttpConnectRequestAdapter, HttpConnector, HttpPooledConnector, HttpPooledConnectorConfig,
};
#[cfg(any(feature = "rustls", feature = "boring"))]
use crate::http::conn::FallbackHttpVersion;
use crate::{
Layer, Service,
dns::client::{DnsConnectorLayer, resolver::DnsAddressResolver},
error::BoxError,
extensions::ExtensionsRef,
http::{
Request, StreamingBody, client::proxy::layer::HttpProxyConnector,
layer::version_adapter::RequestVersionAdapter,
},
net::client::{
ConnectRequest, ConnectionError, ConnectorService, EstablishedClientConnection,
ProxyRouteFailureCache, ProxyRouteFailureCacheConnector, ProxyRoutesConnector,
pool::PooledConnector,
},
service::BoxService,
tcp::client::service::TcpConnector,
};
use std::{marker::PhantomData, time::Duration};
#[cfg(feature = "boring")]
use crate::tls::boring::client as boring_client;
#[cfg(any(feature = "rustls", feature = "boring"))]
use crate::tls::client::TlsClientConfig;
#[cfg(feature = "rustls")]
use crate::tls::rustls::client as rustls_client;
#[cfg(feature = "socks5")]
use crate::{http::client::proxy_connector::ProxyConnector, proxy::socks5::Socks5ProxyConnector};
#[derive(Default)]
pub struct EasyHttpConnectorBuilder<C = (), S = ()> {
connector: C,
_phantom: PhantomData<S>,
}
#[non_exhaustive]
#[derive(Debug)]
pub struct TransportStage;
#[non_exhaustive]
#[derive(Debug)]
pub struct DnsStage;
#[non_exhaustive]
#[derive(Debug)]
pub struct ProxyTunnelStage<const TLS_PROXY: bool = true>;
#[non_exhaustive]
#[derive(Debug)]
pub struct ProxyStage<const PROXY: bool = true>;
#[non_exhaustive]
#[derive(Debug)]
pub struct TlsStage<const PROXY: bool = true>;
#[non_exhaustive]
#[derive(Debug)]
pub struct HttpStage<const PROXY: bool = true>;
#[non_exhaustive]
#[derive(Debug)]
pub struct ProxyRouteFailureCacheStage;
#[non_exhaustive]
#[derive(Debug)]
pub struct PoolStage;
impl EasyHttpConnectorBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_default_transport_connector(
self,
) -> EasyHttpConnectorBuilder<TcpConnector, TransportStage> {
let connector = TcpConnector::default();
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
pub fn with_custom_transport_connector<C>(
self,
connector: C,
) -> EasyHttpConnectorBuilder<C, TransportStage> {
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
}
impl<T, Stage> EasyHttpConnectorBuilder<T, Stage> {
pub fn with_custom_connector<L>(
self,
connector_layer: L,
) -> EasyHttpConnectorBuilder<L::Service, Stage>
where
L: Layer<T>,
{
self.map_connector(|c| connector_layer.into_layer(c))
}
pub fn map_connector<T2>(
self,
map_fn: impl FnOnce(T) -> T2,
) -> EasyHttpConnectorBuilder<T2, Stage> {
let connector = map_fn(self.connector);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
}
impl<T> EasyHttpConnectorBuilder<T, TransportStage> {
pub fn with_default_dns_connector(
self,
) -> EasyHttpConnectorBuilder<crate::dns::client::DnsConnector<T>, DnsStage> {
self.with_dns_connector(DnsConnectorLayer::new())
}
pub fn with_dns_address_resolver<R: DnsAddressResolver + Clone>(
self,
resolver: R,
) -> EasyHttpConnectorBuilder<crate::dns::client::DnsConnector<T, R>, DnsStage> {
self.with_dns_connector(DnsConnectorLayer::with_resolver(resolver))
}
pub fn without_dns_connector(
self,
) -> EasyHttpConnectorBuilder<crate::dns::client::DnsConnector<T>, DnsStage> {
self.with_dns_connector(DnsConnectorLayer::new())
}
pub fn with_dns_connector<L>(
self,
connector_layer: L,
) -> EasyHttpConnectorBuilder<L::Service, DnsStage>
where
L: Layer<T>,
{
let connector = connector_layer.into_layer(self.connector);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
}
impl<T> EasyHttpConnectorBuilder<T, DnsStage> {
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn with_custom_tls_proxy_connector<L>(
self,
connector_layer: L,
) -> EasyHttpConnectorBuilder<L::Service, ProxyTunnelStage<true>>
where
L: Layer<T>,
{
let connector = connector_layer.into_layer(self.connector);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "boring")]
#[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
pub fn with_tls_proxy_support_using_boringssl(
self,
) -> EasyHttpConnectorBuilder<
boring_client::TlsConnector<T, boring_client::ConnectorKindTunnel>,
ProxyTunnelStage<true>,
> {
let connector = boring_client::TlsConnector::tunnel(self.connector, None);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "boring")]
#[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
pub fn with_tls_proxy_support_using_boringssl_config(
self,
config: TlsClientConfig,
) -> EasyHttpConnectorBuilder<
boring_client::TlsConnector<T, boring_client::ConnectorKindTunnel>,
ProxyTunnelStage<true>,
> {
let connector =
boring_client::TlsConnector::tunnel(self.connector, None).with_base_config(config);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "rustls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
pub fn with_tls_proxy_support_using_rustls(
self,
) -> EasyHttpConnectorBuilder<
rustls_client::TlsConnector<T, rustls_client::ConnectorKindTunnel>,
ProxyTunnelStage<true>,
> {
let connector = rustls_client::TlsConnector::tunnel(self.connector, None);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "rustls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
pub fn with_tls_proxy_support_using_rustls_config(
self,
config: TlsClientConfig,
) -> EasyHttpConnectorBuilder<
rustls_client::TlsConnector<T, rustls_client::ConnectorKindTunnel>,
ProxyTunnelStage<true>,
> {
let connector =
rustls_client::TlsConnector::tunnel(self.connector, None).with_base_config(config);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
pub fn without_tls_proxy_support(self) -> EasyHttpConnectorBuilder<T, ProxyTunnelStage<false>> {
EasyHttpConnectorBuilder {
connector: self.connector,
_phantom: PhantomData,
}
}
}
impl<T, const TLS_PROXY: bool> EasyHttpConnectorBuilder<T, ProxyTunnelStage<TLS_PROXY>> {
pub fn with_custom_proxy_connector<L>(
self,
connector_layer: L,
) -> EasyHttpConnectorBuilder<L::Service, ProxyStage<true>>
where
L: Layer<T>,
{
let connector = connector_layer.into_layer(self.connector);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(not(feature = "socks5"))]
pub fn with_proxy_support(
self,
) -> EasyHttpConnectorBuilder<HttpProxyConnector<T>, ProxyStage<true>> {
self.with_http_proxy_support()
}
pub fn with_http_proxy_support(
self,
) -> EasyHttpConnectorBuilder<HttpProxyConnector<T>, ProxyStage<true>> {
let connector =
HttpProxyConnector::optional(self.connector).with_tls_proxy_support(TLS_PROXY);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "socks5")]
#[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
pub fn with_socks5_proxy_support(
self,
) -> EasyHttpConnectorBuilder<Socks5ProxyConnector<T>, ProxyStage<true>> {
let connector = Socks5ProxyConnector::optional(self.connector);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
pub fn without_proxy_support(self) -> EasyHttpConnectorBuilder<T, ProxyStage<false>> {
EasyHttpConnectorBuilder {
connector: self.connector,
_phantom: PhantomData,
}
}
}
impl<T: Clone, const TLS_PROXY: bool> EasyHttpConnectorBuilder<T, ProxyTunnelStage<TLS_PROXY>> {
#[cfg(feature = "socks5")]
#[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
pub fn with_proxy_support(
self,
) -> EasyHttpConnectorBuilder<ProxyConnector<T>, ProxyStage<true>> {
use rama_http_backend::client::proxy::layer::HttpProxyConnectorLayer;
use rama_socks5::Socks5ProxyConnectorLayer;
let connector = ProxyConnector::optional(
self.connector,
Socks5ProxyConnectorLayer::required(),
HttpProxyConnectorLayer::required().with_tls_proxy_support(TLS_PROXY),
);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
}
impl<T, const PROXY: bool> EasyHttpConnectorBuilder<T, ProxyStage<PROXY>> {
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn with_custom_tls_connector<L>(
self,
connector_layer: L,
) -> EasyHttpConnectorBuilder<L::Service, TlsStage<PROXY>>
where
L: Layer<T>,
{
let connector = connector_layer.into_layer(self.connector);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "boring")]
#[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
pub fn with_tls_support_using_boringssl(
self,
config: TlsClientConfig,
) -> EasyHttpConnectorBuilder<boring_client::TlsConnector<T>, TlsStage<PROXY>> {
let connector = boring_client::TlsConnector::auto(self.connector).with_base_config(config);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "boring")]
#[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
pub fn with_tls_support_using_boringssl_and_default_http_version(
self,
config: TlsClientConfig,
default_http_version: rama_http::Version,
) -> EasyHttpConnectorBuilder<
AddInputExtension<boring_client::TlsConnector<T>, FallbackHttpVersion>,
TlsStage<PROXY>,
> {
let connector = boring_client::TlsConnector::auto(self.connector).with_base_config(config);
let connector =
AddInputExtension::new(connector, FallbackHttpVersion(default_http_version))
.with_overwrite(false);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "rustls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
pub fn with_tls_support_using_rustls(
self,
config: TlsClientConfig,
) -> EasyHttpConnectorBuilder<rustls_client::TlsConnector<T>, TlsStage<PROXY>> {
let connector = rustls_client::TlsConnector::auto(self.connector).with_base_config(config);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
#[cfg(feature = "rustls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
pub fn with_tls_support_using_rustls_and_default_http_version(
self,
config: TlsClientConfig,
default_http_version: rama_http::Version,
) -> EasyHttpConnectorBuilder<
AddInputExtension<rustls_client::TlsConnector<T>, FallbackHttpVersion>,
TlsStage<PROXY>,
> {
let connector = rustls_client::TlsConnector::auto(self.connector).with_base_config(config);
let connector =
AddInputExtension::new(connector, FallbackHttpVersion(default_http_version))
.with_overwrite(false);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
pub fn without_tls_support(self) -> EasyHttpConnectorBuilder<T, TlsStage<PROXY>> {
EasyHttpConnectorBuilder {
connector: self.connector,
_phantom: PhantomData,
}
}
}
impl<T, const PROXY: bool> EasyHttpConnectorBuilder<T, TlsStage<PROXY>> {
pub fn with_default_http_connector<Body>(
self,
exec: Executor,
) -> EasyHttpConnectorBuilder<HttpConnector<T, Body>, HttpStage<PROXY>> {
let connector = HttpConnector::new(self.connector, exec);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
pub fn with_custom_http_connector<L>(
self,
connector_layer: L,
) -> EasyHttpConnectorBuilder<L::Service, HttpStage<PROXY>>
where
L: Layer<T>,
{
let connector = connector_layer.into_layer(self.connector);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
}
type DefaultHttpConnector<T> =
RequestVersionAdapter<HttpConnectRequestAdapter<ProxyRoutesConnector<T>>>;
type ConfiguredConnectionBuilder<T> = EasyHttpConnectorBuilder<DefaultHttpConnector<T>, PoolStage>;
type ConfiguredConnectionPoolBuilder<T> =
EasyHttpConnectorBuilder<DefaultHttpConnector<HttpPooledConnector<T>>, PoolStage>;
type ErasedConnector<C> =
BoxService<ConnectRequest, EstablishedClientConnection<C, ConnectRequest>, ConnectionError>;
type DefaultConnectionBuilder<C> =
ConfiguredConnectionBuilder<ProxyRouteFailureCacheConnector<ErasedConnector<C>>>;
type DefaultConnectionPoolBuilder<C> =
ConfiguredConnectionPoolBuilder<ProxyRouteFailureCacheConnector<ErasedConnector<C>>>;
struct ConnectorServiceAdapter<T>(T);
impl<T> Service<ConnectRequest> for ConnectorServiceAdapter<T>
where
T: ConnectorService<ConnectRequest>,
{
type Output = EstablishedClientConnection<T::Connection, ConnectRequest>;
type Error = ConnectionError;
fn serve(
&self,
input: ConnectRequest,
) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send + '_ {
self.0.connect(input)
}
}
fn erase_connector<T>(connector: T) -> ErasedConnector<T::Connection>
where
T: ConnectorService<ConnectRequest>,
{
ConnectorServiceAdapter(connector).boxed()
}
fn finalize_http_connector<T>(connector: T) -> DefaultHttpConnector<T> {
let connector = ProxyRoutesConnector::new(connector);
let connector = HttpConnectRequestAdapter::new(connector);
RequestVersionAdapter::new(connector)
}
fn finish_without_connection_pool<T, Stage>(
builder: EasyHttpConnectorBuilder<T, Stage>,
) -> ConfiguredConnectionBuilder<T>
where
T: ConnectorService<ConnectRequest>,
{
EasyHttpConnectorBuilder {
connector: finalize_http_connector(builder.connector),
_phantom: PhantomData,
}
}
fn finish_with_connection_pool<T, Stage>(
builder: EasyHttpConnectorBuilder<T, Stage>,
config: HttpPooledConnectorConfig,
) -> Result<ConfiguredConnectionPoolBuilder<T>, BoxError>
where
T: ConnectorService<ConnectRequest>,
{
let connector = config.try_build_connector(builder.connector)?;
Ok(EasyHttpConnectorBuilder {
connector: finalize_http_connector(connector),
_phantom: PhantomData,
})
}
fn finish_with_default_connection_pool<T, Stage>(
builder: EasyHttpConnectorBuilder<T, Stage>,
) -> ConfiguredConnectionPoolBuilder<T>
where
T: ConnectorService<ConnectRequest>,
{
let connector = HttpPooledConnectorConfig::build_default_connector(builder.connector);
EasyHttpConnectorBuilder {
connector: finalize_http_connector(connector),
_phantom: PhantomData,
}
}
fn finish_with_custom_connection_pool<T, Stage, P, R>(
builder: EasyHttpConnectorBuilder<T, Stage>,
pool: P,
req_to_conn_id: R,
wait_for_pool_timeout: Option<Duration>,
) -> EasyHttpConnectorBuilder<PooledConnector<T, P, R>, PoolStage> {
let connector = PooledConnector::new(builder.connector, pool, req_to_conn_id)
.maybe_with_wait_for_pool_timeout(wait_for_pool_timeout);
EasyHttpConnectorBuilder {
connector,
_phantom: PhantomData,
}
}
impl<T, const PROXY: bool> EasyHttpConnectorBuilder<T, HttpStage<PROXY>> {
#[must_use]
pub fn with_proxy_route_failure_cache(
self,
cache: ProxyRouteFailureCache,
) -> EasyHttpConnectorBuilder<
ProxyRouteFailureCacheConnector<ErasedConnector<T::Connection>>,
ProxyRouteFailureCacheStage,
>
where
T: ConnectorService<ConnectRequest>,
{
EasyHttpConnectorBuilder {
connector: ProxyRouteFailureCacheConnector::new(erase_connector(self.connector), cache),
_phantom: PhantomData,
}
}
#[must_use]
pub fn without_proxy_route_failure_cache(
self,
) -> EasyHttpConnectorBuilder<T, ProxyRouteFailureCacheStage> {
EasyHttpConnectorBuilder {
connector: self.connector,
_phantom: PhantomData,
}
}
}
impl<T> EasyHttpConnectorBuilder<T, HttpStage<true>> {
pub fn without_connection_pool(self) -> DefaultConnectionBuilder<T::Connection>
where
T: ConnectorService<ConnectRequest>,
{
finish_without_connection_pool(
self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
)
}
pub fn try_with_connection_pool(
self,
config: HttpPooledConnectorConfig,
) -> Result<DefaultConnectionPoolBuilder<T::Connection>, BoxError>
where
T: ConnectorService<ConnectRequest>,
{
finish_with_connection_pool(
self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
config,
)
}
pub fn with_default_connection_pool(self) -> DefaultConnectionPoolBuilder<T::Connection>
where
T: ConnectorService<ConnectRequest>,
{
finish_with_default_connection_pool(
self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
)
}
pub fn with_custom_connection_pool<P, R>(
self,
pool: P,
req_to_conn_id: R,
wait_for_pool_timeout: Option<Duration>,
) -> EasyHttpConnectorBuilder<
PooledConnector<ProxyRouteFailureCacheConnector<ErasedConnector<T::Connection>>, P, R>,
PoolStage,
>
where
T: ConnectorService<ConnectRequest>,
{
finish_with_custom_connection_pool(
self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
pool,
req_to_conn_id,
wait_for_pool_timeout,
)
}
}
impl<T> EasyHttpConnectorBuilder<T, HttpStage<false>> {
pub fn without_connection_pool(self) -> ConfiguredConnectionBuilder<T>
where
T: ConnectorService<ConnectRequest>,
{
finish_without_connection_pool(self)
}
pub fn try_with_connection_pool(
self,
config: HttpPooledConnectorConfig,
) -> Result<ConfiguredConnectionPoolBuilder<T>, BoxError>
where
T: ConnectorService<ConnectRequest>,
{
finish_with_connection_pool(self, config)
}
pub fn with_default_connection_pool(self) -> ConfiguredConnectionPoolBuilder<T>
where
T: ConnectorService<ConnectRequest>,
{
finish_with_default_connection_pool(self)
}
pub fn with_custom_connection_pool<P, R>(
self,
pool: P,
req_to_conn_id: R,
wait_for_pool_timeout: Option<Duration>,
) -> EasyHttpConnectorBuilder<PooledConnector<T, P, R>, PoolStage> {
finish_with_custom_connection_pool(self, pool, req_to_conn_id, wait_for_pool_timeout)
}
}
impl<T> EasyHttpConnectorBuilder<T, ProxyRouteFailureCacheStage> {
pub fn without_connection_pool(self) -> ConfiguredConnectionBuilder<T>
where
T: ConnectorService<ConnectRequest>,
{
finish_without_connection_pool(self)
}
pub fn try_with_connection_pool(
self,
config: HttpPooledConnectorConfig,
) -> Result<ConfiguredConnectionPoolBuilder<T>, BoxError>
where
T: ConnectorService<ConnectRequest>,
{
finish_with_connection_pool(self, config)
}
pub fn with_default_connection_pool(self) -> ConfiguredConnectionPoolBuilder<T>
where
T: ConnectorService<ConnectRequest>,
{
finish_with_default_connection_pool(self)
}
pub fn with_custom_connection_pool<P, R>(
self,
pool: P,
req_to_conn_id: R,
wait_for_pool_timeout: Option<Duration>,
) -> EasyHttpConnectorBuilder<PooledConnector<T, P, R>, PoolStage> {
finish_with_custom_connection_pool(self, pool, req_to_conn_id, wait_for_pool_timeout)
}
}
impl<T> EasyHttpConnectorBuilder<T, PoolStage> {
pub fn build_client<Body, ModifiedBody, ConnResponse>(
self,
) -> super::EasyHttpWebClient<Body, T::Output, ()>
where
Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
ModifiedBody:
StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
T: Service<
Request<Body>,
Output = EstablishedClientConnection<ConnResponse, Request<ModifiedBody>>,
Error: Into<BoxError>,
>,
ConnResponse: ExtensionsRef,
{
super::EasyHttpWebClient::new(self.connector)
}
}
impl<T, S> EasyHttpConnectorBuilder<T, S> {
pub fn build_connector(self) -> T {
self.connector
}
}