1use rama_core::rt::Executor;
2
3use super::{
4 HttpConnectRequestAdapter, HttpConnector, HttpPooledConnector, HttpPooledConnectorConfig,
5};
6use crate::{
7 Layer, Service,
8 dns::client::{
9 DnsConnector, DnsConnectorLayer, GlobalDnsResolver, resolver::DnsAddressResolver,
10 },
11 error::BoxError,
12 extensions::ExtensionsRef,
13 http::{
14 Request, StreamingBody, client::proxy::layer::HttpProxyConnector,
15 layer::version_adapter::RequestVersionAdapter,
16 },
17 net::client::{
18 ConnectRequest, ConnectionError, ConnectorService, EstablishedClientConnection,
19 MapEstablishedConnection, ProxyRouteFailureCache, ProxyRouteFailureCacheConnector,
20 ProxyRoutesConnector, pool::PooledConnector,
21 },
22 service::BoxService,
23 tcp::client::service::TcpConnector,
24};
25use rama_http::layer::{
26 alt_svc::{AltSvc, AltSvcCache, AltSvcLayer},
27 http_service::HttpServiceConnector,
28};
29use rama_utils::macros::generate_set_and_with;
30use std::time::Duration;
31
32#[cfg(feature = "boring")]
33use crate::tls::boring::client as boring_client;
34
35#[cfg(feature = "rustls")]
36use crate::tls::rustls::client as rustls_client;
37#[cfg(any(feature = "rustls", feature = "boring"))]
38use {
39 crate::http::conn::FallbackHttpVersion, rama_core::layer::AddInputExtension,
40 rama_tls::client::TlsClientConfig,
41};
42
43#[cfg(feature = "socks5")]
44use crate::{http::client::proxy_connector::ProxyConnector, proxy::socks5::Socks5ProxyConnector};
45
46#[derive(Default)]
48pub struct EasyHttpConnectorBuilder<C = (), S = (), D = ()> {
49 connector: C,
50 stage: S,
51 dns: D,
53}
54
55#[non_exhaustive]
56#[derive(Debug, Default)]
57pub struct TransportStage;
58
59#[non_exhaustive]
60#[derive(Debug, Default)]
61pub struct DnsStage;
62
63#[non_exhaustive]
64#[derive(Debug, Default)]
65pub struct ProxyTunnelStage<const TLS_PROXY: bool = true>;
66
67#[non_exhaustive]
68#[derive(Debug, Default)]
69pub struct ProxyStage<const PROXY: bool = true>;
70
71#[non_exhaustive]
72#[derive(Debug, Default)]
73pub struct TlsStage<const PROXY: bool = true>;
74
75#[non_exhaustive]
76#[derive(Debug, Default)]
77pub struct HttpStage<const PROXY: bool = true>;
78
79#[non_exhaustive]
80#[derive(Debug, Default)]
81pub struct ProxyRouteFailureCacheStage;
82
83#[non_exhaustive]
84#[derive(Debug, Default)]
85pub struct PoolStage;
86
87impl EasyHttpConnectorBuilder {
88 #[must_use]
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 #[must_use]
94 pub fn with_default_transport_connector(
95 self,
96 ) -> EasyHttpConnectorBuilder<TcpConnector, TransportStage> {
97 let connector = TcpConnector::default();
98 EasyHttpConnectorBuilder {
99 dns: self.dns,
100 connector,
101 stage: Default::default(),
102 }
103 }
104
105 pub fn with_custom_transport_connector<C>(
107 self,
108 connector: C,
109 ) -> EasyHttpConnectorBuilder<C, TransportStage> {
110 EasyHttpConnectorBuilder {
111 dns: self.dns,
112 connector,
113 stage: Default::default(),
114 }
115 }
116}
117
118impl<T, Stage, D> EasyHttpConnectorBuilder<T, Stage, D> {
119 pub fn with_custom_connector<L>(
124 self,
125 connector_layer: L,
126 ) -> EasyHttpConnectorBuilder<L::Service, Stage, D>
127 where
128 L: Layer<T>,
129 {
130 self.map_connector(|c| connector_layer.into_layer(c))
131 }
132
133 pub fn map_connector<T2>(
138 self,
139 map_fn: impl FnOnce(T) -> T2,
140 ) -> EasyHttpConnectorBuilder<T2, Stage, D> {
141 let connector = map_fn(self.connector);
142 EasyHttpConnectorBuilder {
143 dns: self.dns,
144 connector,
145 stage: self.stage,
146 }
147 }
148}
149
150impl<T> EasyHttpConnectorBuilder<T, TransportStage> {
151 pub fn with_default_dns_connector(
153 self,
154 ) -> EasyHttpConnectorBuilder<DnsConnector<T>, DnsStage, DnsConnectorLayer> {
155 self.with_dns_address_resolver(GlobalDnsResolver::new())
156 }
157
158 pub fn with_dns_address_resolver<R: DnsAddressResolver + Clone>(
160 self,
161 resolver: R,
162 ) -> EasyHttpConnectorBuilder<DnsConnector<T, R>, DnsStage, DnsConnectorLayer<R>> {
163 self.with_dns_connector(DnsConnectorLayer::with_resolver(resolver))
164 }
165
166 pub fn without_dns_connector(self) -> EasyHttpConnectorBuilder<T, DnsStage> {
168 self.with_dns_connector(())
169 }
170
171 pub fn with_dns_connector<L: Layer<T>>(
178 self,
179 layer: L,
180 ) -> EasyHttpConnectorBuilder<L::Service, DnsStage, L> {
181 let connector = layer.layer(self.connector);
182 EasyHttpConnectorBuilder {
183 connector,
184 stage: DnsStage,
185 dns: layer,
186 }
187 }
188}
189
190impl<T, D> EasyHttpConnectorBuilder<T, DnsStage, D> {
191 pub fn with_custom_tls_proxy_connector<L>(
198 self,
199 connector_layer: L,
200 ) -> EasyHttpConnectorBuilder<L::Service, ProxyTunnelStage<true>, D>
201 where
202 L: Layer<T>,
203 {
204 let connector = connector_layer.into_layer(self.connector);
205 EasyHttpConnectorBuilder {
206 dns: self.dns,
207 connector,
208 stage: Default::default(),
209 }
210 }
211
212 #[cfg(feature = "boring")]
213 #[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
214 pub fn with_tls_proxy_support_using_boringssl(
220 self,
221 ) -> EasyHttpConnectorBuilder<
222 boring_client::TlsConnector<T, boring_client::ConnectorKindTunnel>,
223 ProxyTunnelStage<true>,
224 D,
225 > {
226 let connector = boring_client::TlsConnector::tunnel(self.connector, None);
227 EasyHttpConnectorBuilder {
228 dns: self.dns,
229 connector,
230 stage: Default::default(),
231 }
232 }
233
234 #[cfg(feature = "boring")]
235 #[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
236 pub fn with_tls_proxy_support_using_boringssl_config(
242 self,
243 config: TlsClientConfig,
244 ) -> EasyHttpConnectorBuilder<
245 boring_client::TlsConnector<T, boring_client::ConnectorKindTunnel>,
246 ProxyTunnelStage<true>,
247 D,
248 > {
249 let connector =
250 boring_client::TlsConnector::tunnel(self.connector, None).with_base_config(config);
251 EasyHttpConnectorBuilder {
252 dns: self.dns,
253 connector,
254 stage: Default::default(),
255 }
256 }
257
258 #[cfg(feature = "rustls")]
259 #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
260 pub fn with_tls_proxy_support_using_rustls(
266 self,
267 ) -> EasyHttpConnectorBuilder<
268 rustls_client::TlsConnector<T, rustls_client::ConnectorKindTunnel>,
269 ProxyTunnelStage<true>,
270 D,
271 > {
272 let connector = rustls_client::TlsConnector::tunnel(self.connector, None);
273
274 EasyHttpConnectorBuilder {
275 dns: self.dns,
276 connector,
277 stage: Default::default(),
278 }
279 }
280
281 #[cfg(feature = "rustls")]
282 #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
283 pub fn with_tls_proxy_support_using_rustls_config(
289 self,
290 config: TlsClientConfig,
291 ) -> EasyHttpConnectorBuilder<
292 rustls_client::TlsConnector<T, rustls_client::ConnectorKindTunnel>,
293 ProxyTunnelStage<true>,
294 D,
295 > {
296 let connector =
297 rustls_client::TlsConnector::tunnel(self.connector, None).with_base_config(config);
298
299 EasyHttpConnectorBuilder {
300 dns: self.dns,
301 connector,
302 stage: Default::default(),
303 }
304 }
305
306 pub fn without_tls_proxy_support(
312 self,
313 ) -> EasyHttpConnectorBuilder<T, ProxyTunnelStage<false>, D> {
314 EasyHttpConnectorBuilder {
315 dns: self.dns,
316 connector: self.connector,
317 stage: Default::default(),
318 }
319 }
320}
321
322impl<T, D, const TLS_PROXY: bool> EasyHttpConnectorBuilder<T, ProxyTunnelStage<TLS_PROXY>, D> {
323 pub fn with_custom_proxy_connector<L>(
325 self,
326 connector_layer: L,
327 ) -> EasyHttpConnectorBuilder<L::Service, ProxyStage<true>, D>
328 where
329 L: Layer<T>,
330 {
331 let connector = connector_layer.into_layer(self.connector);
332 EasyHttpConnectorBuilder {
333 dns: self.dns,
334 connector,
335 stage: Default::default(),
336 }
337 }
338
339 #[cfg(not(feature = "socks5"))]
340 pub fn with_proxy_support(
350 self,
351 ) -> EasyHttpConnectorBuilder<HttpProxyConnector<T>, ProxyStage<true>, D> {
352 self.with_http_proxy_support()
353 }
354
355 pub fn with_http_proxy_support(
363 self,
364 ) -> EasyHttpConnectorBuilder<HttpProxyConnector<T>, ProxyStage<true>, D> {
365 let connector =
366 HttpProxyConnector::optional(self.connector).with_tls_proxy_support(TLS_PROXY);
367
368 EasyHttpConnectorBuilder {
369 dns: self.dns,
370 connector,
371 stage: Default::default(),
372 }
373 }
374
375 #[cfg(feature = "socks5")]
376 #[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
377 pub fn with_socks5_proxy_support(
381 self,
382 ) -> EasyHttpConnectorBuilder<Socks5ProxyConnector<T>, ProxyStage<true>, D> {
383 let connector = Socks5ProxyConnector::optional(self.connector);
384
385 EasyHttpConnectorBuilder {
386 dns: self.dns,
387 connector,
388 stage: Default::default(),
389 }
390 }
391
392 pub fn without_proxy_support(self) -> EasyHttpConnectorBuilder<T, ProxyStage<false>, D> {
394 EasyHttpConnectorBuilder {
395 dns: self.dns,
396 connector: self.connector,
397 stage: Default::default(),
398 }
399 }
400}
401
402impl<T: Clone, D, const TLS_PROXY: bool>
403 EasyHttpConnectorBuilder<T, ProxyTunnelStage<TLS_PROXY>, D>
404{
405 #[cfg(feature = "socks5")]
406 #[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
407 pub fn with_proxy_support(
415 self,
416 ) -> EasyHttpConnectorBuilder<ProxyConnector<T>, ProxyStage<true>, D> {
417 use rama_http_backend::client::proxy::layer::HttpProxyConnectorLayer;
418 use rama_socks5::Socks5ProxyConnectorLayer;
419
420 let connector = ProxyConnector::optional(
421 self.connector,
422 Socks5ProxyConnectorLayer::required(),
423 HttpProxyConnectorLayer::required().with_tls_proxy_support(TLS_PROXY),
424 );
425
426 EasyHttpConnectorBuilder {
427 dns: self.dns,
428 connector,
429 stage: Default::default(),
430 }
431 }
432}
433
434impl<T, D, const PROXY: bool> EasyHttpConnectorBuilder<T, ProxyStage<PROXY>, D> {
435 pub fn with_custom_tls_connector<L>(
445 self,
446 connector_layer: L,
447 ) -> EasyHttpConnectorBuilder<L::Service, TlsStage<PROXY>, D>
448 where
449 L: Layer<T>,
450 {
451 let connector = connector_layer.into_layer(self.connector);
452
453 EasyHttpConnectorBuilder {
454 dns: self.dns,
455 connector,
456 stage: Default::default(),
457 }
458 }
459
460 #[cfg(feature = "boring")]
461 #[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
462 pub fn with_tls_support_using_boringssl(
467 self,
468 config: TlsClientConfig,
469 ) -> EasyHttpConnectorBuilder<boring_client::TlsConnector<T>, TlsStage<PROXY>, D> {
470 let connector = boring_client::TlsConnector::auto(self.connector).with_base_config(config);
471
472 EasyHttpConnectorBuilder {
473 dns: self.dns,
474 connector,
475 stage: Default::default(),
476 }
477 }
478
479 #[cfg(feature = "boring")]
480 #[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
481 pub fn with_tls_support_using_boringssl_and_default_http_version(
491 self,
492 config: TlsClientConfig,
493 default_http_version: rama_http::Version,
494 ) -> EasyHttpConnectorBuilder<
495 AddInputExtension<boring_client::TlsConnector<T>, FallbackHttpVersion>,
496 TlsStage<PROXY>,
497 D,
498 > {
499 let connector = boring_client::TlsConnector::auto(self.connector).with_base_config(config);
500 let connector =
501 AddInputExtension::new(connector, FallbackHttpVersion(default_http_version))
502 .with_overwrite(false);
503
504 EasyHttpConnectorBuilder {
505 dns: self.dns,
506 connector,
507 stage: Default::default(),
508 }
509 }
510
511 #[cfg(feature = "rustls")]
512 #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
513 pub fn with_tls_support_using_rustls(
518 self,
519 config: TlsClientConfig,
520 ) -> EasyHttpConnectorBuilder<rustls_client::TlsConnector<T>, TlsStage<PROXY>, D> {
521 let connector = rustls_client::TlsConnector::auto(self.connector).with_base_config(config);
522
523 EasyHttpConnectorBuilder {
524 dns: self.dns,
525 connector,
526 stage: Default::default(),
527 }
528 }
529
530 #[cfg(feature = "rustls")]
531 #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
532 pub fn with_tls_support_using_rustls_and_default_http_version(
542 self,
543 config: TlsClientConfig,
544 default_http_version: rama_http::Version,
545 ) -> EasyHttpConnectorBuilder<
546 AddInputExtension<rustls_client::TlsConnector<T>, FallbackHttpVersion>,
547 TlsStage<PROXY>,
548 D,
549 > {
550 let connector = rustls_client::TlsConnector::auto(self.connector).with_base_config(config);
551 let connector =
552 AddInputExtension::new(connector, FallbackHttpVersion(default_http_version))
553 .with_overwrite(false);
554
555 EasyHttpConnectorBuilder {
556 dns: self.dns,
557 connector,
558 stage: Default::default(),
559 }
560 }
561
562 pub fn without_tls_support(self) -> EasyHttpConnectorBuilder<T, TlsStage<PROXY>, D> {
564 EasyHttpConnectorBuilder {
565 dns: self.dns,
566 connector: self.connector,
567 stage: Default::default(),
568 }
569 }
570}
571
572impl<T, D, const PROXY: bool> EasyHttpConnectorBuilder<T, TlsStage<PROXY>, D> {
573 pub fn with_default_http_connector<Body>(
575 self,
576 exec: Executor,
577 ) -> EasyHttpConnectorBuilder<HttpConnector<T, Body>, HttpStage<PROXY>, D> {
578 let connector = HttpConnector::new(self.connector, exec);
579
580 EasyHttpConnectorBuilder {
581 dns: self.dns,
582 connector,
583 stage: HttpStage,
584 }
585 }
586
587 pub fn with_custom_http_connector<L>(
589 self,
590 connector_layer: L,
591 ) -> EasyHttpConnectorBuilder<L::Service, HttpStage<PROXY>, D>
592 where
593 L: Layer<T>,
594 {
595 let connector = connector_layer.into_layer(self.connector);
596
597 EasyHttpConnectorBuilder {
598 dns: self.dns,
599 connector,
600 stage: HttpStage,
601 }
602 }
603}
604
605impl<T, Body, D, const PROXY: bool>
606 EasyHttpConnectorBuilder<HttpConnector<T, Body>, HttpStage<PROXY>, D>
607{
608 pub fn with_http3_connector<C>(
614 self,
615 connector: C,
616 ) -> EasyHttpConnectorBuilder<
617 HttpConnector<super::HttpTransportConnector<T, C>, Body>,
618 HttpStage<PROXY>,
619 D,
620 > {
621 EasyHttpConnectorBuilder {
622 dns: self.dns,
623 connector: self.connector.with_http3_connector(connector),
624 stage: self.stage,
625 }
626 }
627
628 pub fn with_http3_support(
634 self,
635 connector: super::Http3Connector,
636 ) -> EasyHttpConnectorBuilder<
637 HttpConnector<super::HttpTransportConnector<T, D::Service>, Body>,
638 HttpStage<PROXY>,
639 D,
640 >
641 where
642 D: Layer<super::Http3Connector>,
643 {
644 let connector = self.dns.layer(connector);
645 self.with_http3_connector(connector)
646 }
647}
648
649type DefaultHttpConnector<T> = RequestVersionAdapter<
650 HttpConnectRequestAdapter<
651 MapEstablishedConnection<
652 HttpServiceConnector<ProxyRoutesConnector<T>>,
653 AltSvcConnectionLayer,
654 >,
655 >,
656>;
657
658#[doc(hidden)]
663#[derive(Clone, Debug)]
664pub struct AltSvcConnectionLayer {
665 cache: Option<AltSvcCache>,
666}
667
668impl<S> Layer<S> for AltSvcConnectionLayer {
669 type Service = AltSvc<S>;
670
671 fn layer(&self, inner: S) -> Self::Service {
672 match &self.cache {
673 Some(cache) => AltSvcLayer::new(cache.clone()).layer(inner),
674 None => AltSvc::passthrough(inner),
675 }
676 }
677}
678
679type ConfiguredConnectionBuilder<T> = EasyHttpConnectorBuilder<DefaultHttpConnector<T>, PoolStage>;
680
681type ConfiguredConnectionPoolBuilder<T> =
682 EasyHttpConnectorBuilder<DefaultHttpConnector<HttpPooledConnector<T>>, PoolStage>;
683
684type ErasedConnector<C> =
685 BoxService<ConnectRequest, EstablishedClientConnection<C, ConnectRequest>, ConnectionError>;
686
687type DefaultConnectionBuilder<C> =
688 ConfiguredConnectionBuilder<ProxyRouteFailureCacheConnector<ErasedConnector<C>>>;
689
690type DefaultConnectionPoolBuilder<C> =
691 ConfiguredConnectionPoolBuilder<ProxyRouteFailureCacheConnector<ErasedConnector<C>>>;
692
693struct ConnectorServiceAdapter<T>(T);
698
699impl<T> Service<ConnectRequest> for ConnectorServiceAdapter<T>
700where
701 T: ConnectorService<ConnectRequest>,
702{
703 type Output = EstablishedClientConnection<T::Connection, ConnectRequest>;
704 type Error = ConnectionError;
705
706 fn serve(
707 &self,
708 input: ConnectRequest,
709 ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send + '_ {
710 self.0.connect(input)
711 }
712}
713
714fn erase_connector<T>(connector: T) -> ErasedConnector<T::Connection>
715where
716 T: ConnectorService<ConnectRequest>,
717{
718 ConnectorServiceAdapter(connector).boxed()
719}
720
721fn finalize_http_connector<T>(connector: T) -> DefaultHttpConnector<T> {
722 let connector = ProxyRoutesConnector::new(connector);
723 let cache = AltSvcCache::default();
724 let connector = HttpServiceConnector::new(connector).with_cache(cache.clone());
725 adapt_http_service_connector(connector, Some(cache))
726}
727
728fn adapt_http_service_connector<T>(
729 connector: HttpServiceConnector<ProxyRoutesConnector<T>>,
730 cache: Option<AltSvcCache>,
731) -> DefaultHttpConnector<T> {
732 let connector = MapEstablishedConnection::new(connector, AltSvcConnectionLayer { cache });
733 RequestVersionAdapter::new(HttpConnectRequestAdapter::new(connector))
734}
735
736impl<T> EasyHttpConnectorBuilder<DefaultHttpConnector<T>, PoolStage> {
737 #[must_use]
739 pub fn without_alt_svc(self) -> Self {
740 self.maybe_with_alt_svc_cache(None)
741 }
742
743 generate_set_and_with! {
744 pub fn alt_svc_cache(mut self, cache: Option<AltSvcCache>) -> Self {
746 let connector = self.connector.get_mut().get_mut();
747 connector.get_mut().maybe_set_cache(cache.clone());
748 connector.layer_mut().cache = cache;
749 self
750 }
751 }
752}
753
754fn finish_without_connection_pool<T, Stage, D>(
755 builder: EasyHttpConnectorBuilder<T, Stage, D>,
756) -> ConfiguredConnectionBuilder<T>
757where
758 T: ConnectorService<ConnectRequest>,
759{
760 EasyHttpConnectorBuilder {
761 dns: (),
762 connector: finalize_http_connector(builder.connector),
763 stage: Default::default(),
764 }
765}
766
767fn finish_with_connection_pool<T, Stage, D>(
768 builder: EasyHttpConnectorBuilder<T, Stage, D>,
769 config: HttpPooledConnectorConfig,
770) -> Result<ConfiguredConnectionPoolBuilder<T>, BoxError>
771where
772 T: ConnectorService<ConnectRequest>,
773{
774 let connector = config.try_build_connector(builder.connector)?;
775 Ok(EasyHttpConnectorBuilder {
776 dns: (),
777 connector: finalize_http_connector(connector),
778 stage: Default::default(),
779 })
780}
781
782fn finish_with_default_connection_pool<T, Stage, D>(
783 builder: EasyHttpConnectorBuilder<T, Stage, D>,
784) -> ConfiguredConnectionPoolBuilder<T>
785where
786 T: ConnectorService<ConnectRequest>,
787{
788 let connector = HttpPooledConnectorConfig::build_default_connector(builder.connector);
789 EasyHttpConnectorBuilder {
790 dns: (),
791 connector: finalize_http_connector(connector),
792 stage: Default::default(),
793 }
794}
795
796fn finish_with_custom_connection_pool<T, Stage, D, P, R>(
797 builder: EasyHttpConnectorBuilder<T, Stage, D>,
798 pool: P,
799 req_to_conn_id: R,
800 wait_for_pool_timeout: Option<Duration>,
801) -> EasyHttpConnectorBuilder<PooledConnector<T, P, R>, PoolStage> {
802 let connector = PooledConnector::new(builder.connector, pool, req_to_conn_id)
803 .maybe_with_wait_for_pool_timeout(wait_for_pool_timeout);
804 EasyHttpConnectorBuilder {
805 dns: (),
806 connector,
807 stage: Default::default(),
808 }
809}
810
811impl<T, D, const PROXY: bool> EasyHttpConnectorBuilder<T, HttpStage<PROXY>, D> {
812 #[must_use]
818 pub fn with_proxy_route_failure_cache(
819 self,
820 cache: ProxyRouteFailureCache,
821 ) -> EasyHttpConnectorBuilder<
822 ProxyRouteFailureCacheConnector<ErasedConnector<T::Connection>>,
823 ProxyRouteFailureCacheStage,
824 D,
825 >
826 where
827 T: ConnectorService<ConnectRequest>,
828 {
829 EasyHttpConnectorBuilder {
830 dns: self.dns,
831 connector: ProxyRouteFailureCacheConnector::new(erase_connector(self.connector), cache),
832 stage: ProxyRouteFailureCacheStage,
833 }
834 }
835
836 #[must_use]
838 pub fn without_proxy_route_failure_cache(
839 self,
840 ) -> EasyHttpConnectorBuilder<T, ProxyRouteFailureCacheStage, D> {
841 EasyHttpConnectorBuilder {
842 dns: self.dns,
843 connector: self.connector,
844 stage: ProxyRouteFailureCacheStage,
845 }
846 }
847}
848
849impl<T, D> EasyHttpConnectorBuilder<T, HttpStage<true>, D> {
850 pub fn without_connection_pool(self) -> DefaultConnectionBuilder<T::Connection>
856 where
857 T: ConnectorService<ConnectRequest>,
858 {
859 finish_without_connection_pool(
860 self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
861 )
862 }
863
864 pub fn try_with_connection_pool(
885 self,
886 config: HttpPooledConnectorConfig,
887 ) -> Result<DefaultConnectionPoolBuilder<T::Connection>, BoxError>
888 where
889 T: ConnectorService<ConnectRequest>,
890 {
891 finish_with_connection_pool(
892 self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
893 config,
894 )
895 }
896
897 pub fn with_default_connection_pool(self) -> DefaultConnectionPoolBuilder<T::Connection>
903 where
904 T: ConnectorService<ConnectRequest>,
905 {
906 finish_with_default_connection_pool(
907 self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
908 )
909 }
910
911 pub fn with_custom_connection_pool<P, R>(
937 self,
938 pool: P,
939 req_to_conn_id: R,
940 wait_for_pool_timeout: Option<Duration>,
941 ) -> EasyHttpConnectorBuilder<
942 PooledConnector<ProxyRouteFailureCacheConnector<ErasedConnector<T::Connection>>, P, R>,
943 PoolStage,
944 >
945 where
946 T: ConnectorService<ConnectRequest>,
947 {
948 finish_with_custom_connection_pool(
949 self.with_proxy_route_failure_cache(ProxyRouteFailureCache::default()),
950 pool,
951 req_to_conn_id,
952 wait_for_pool_timeout,
953 )
954 }
955}
956
957impl<T, D> EasyHttpConnectorBuilder<T, HttpStage<false>, D> {
958 pub fn without_connection_pool(self) -> ConfiguredConnectionBuilder<T>
964 where
965 T: ConnectorService<ConnectRequest>,
966 {
967 finish_without_connection_pool(self)
968 }
969
970 pub fn try_with_connection_pool(
972 self,
973 config: HttpPooledConnectorConfig,
974 ) -> Result<ConfiguredConnectionPoolBuilder<T>, BoxError>
975 where
976 T: ConnectorService<ConnectRequest>,
977 {
978 finish_with_connection_pool(self, config)
979 }
980
981 pub fn with_default_connection_pool(self) -> ConfiguredConnectionPoolBuilder<T>
984 where
985 T: ConnectorService<ConnectRequest>,
986 {
987 finish_with_default_connection_pool(self)
988 }
989
990 pub fn with_custom_connection_pool<P, R>(
997 self,
998 pool: P,
999 req_to_conn_id: R,
1000 wait_for_pool_timeout: Option<Duration>,
1001 ) -> EasyHttpConnectorBuilder<PooledConnector<T, P, R>, PoolStage> {
1002 finish_with_custom_connection_pool(self, pool, req_to_conn_id, wait_for_pool_timeout)
1003 }
1004}
1005
1006impl<T, D> EasyHttpConnectorBuilder<T, ProxyRouteFailureCacheStage, D> {
1007 pub fn without_connection_pool(self) -> ConfiguredConnectionBuilder<T>
1009 where
1010 T: ConnectorService<ConnectRequest>,
1011 {
1012 finish_without_connection_pool(self)
1013 }
1014
1015 pub fn try_with_connection_pool(
1017 self,
1018 config: HttpPooledConnectorConfig,
1019 ) -> Result<ConfiguredConnectionPoolBuilder<T>, BoxError>
1020 where
1021 T: ConnectorService<ConnectRequest>,
1022 {
1023 finish_with_connection_pool(self, config)
1024 }
1025
1026 pub fn with_default_connection_pool(self) -> ConfiguredConnectionPoolBuilder<T>
1029 where
1030 T: ConnectorService<ConnectRequest>,
1031 {
1032 finish_with_default_connection_pool(self)
1033 }
1034
1035 pub fn with_custom_connection_pool<P, R>(
1048 self,
1049 pool: P,
1050 req_to_conn_id: R,
1051 wait_for_pool_timeout: Option<Duration>,
1052 ) -> EasyHttpConnectorBuilder<PooledConnector<T, P, R>, PoolStage> {
1053 finish_with_custom_connection_pool(self, pool, req_to_conn_id, wait_for_pool_timeout)
1054 }
1055}
1056
1057impl<T> EasyHttpConnectorBuilder<T, PoolStage> {
1058 pub fn build_client<Body, ModifiedBody, ConnResponse>(
1060 self,
1061 ) -> super::EasyHttpWebClient<Body, T::Output, ()>
1062 where
1063 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
1064 ModifiedBody:
1065 StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
1066 T: Service<
1067 Request<Body>,
1068 Output = EstablishedClientConnection<ConnResponse, Request<ModifiedBody>>,
1069 Error: Into<BoxError>,
1070 >,
1071 ConnResponse: ExtensionsRef,
1072 {
1073 super::EasyHttpWebClient::new(self.connector)
1074 }
1075}
1076
1077impl<T, S, D> EasyHttpConnectorBuilder<T, S, D> {
1078 pub fn build_connector(self) -> T {
1080 self.connector
1081 }
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use super::*;
1087 use crate::http::{Body, client::HttpConnIdentifier};
1088 use rama_net::{
1089 Protocol,
1090 address::HostWithPort,
1091 client::{
1092 ConnectRequest,
1093 pool::{ConnID as _, ReqToConnID},
1094 },
1095 };
1096 use rama_tls::client::{ServerVerifyMode, TlsPoolId, TlsServerVerify};
1097 use rama_utils::octets::kib;
1098
1099 use rama_core::layer::layer_fn;
1100 #[cfg(any(
1101 feature = "boring",
1102 all(feature = "rustls", any(feature = "ring", feature = "aws-lc"))
1103 ))]
1104 use {
1105 crate::http::client::Http3Connector,
1106 crate::quic::Endpoint,
1107 rama_core::futures::{Stream, stream},
1108 rama_http::layer::http_service::HttpServiceAttempt,
1109 rama_net::{
1110 address::{Domain, SocketAddress},
1111 client::ConnectionErrorKind,
1112 http::TargetHttpVersion as RequestedVersion,
1113 },
1114 std::{
1115 net::{Ipv4Addr, Ipv6Addr, SocketAddr},
1116 sync::{
1117 Arc,
1118 atomic::{AtomicUsize, Ordering},
1119 },
1120 },
1121 };
1122 #[cfg(feature = "tls")]
1123 use {
1124 crate::http::{
1125 HeaderMap, Version,
1126 conn::{HttpOrigin, SelectedHttpService},
1127 header::ALT_SVC,
1128 },
1129 rama_core::{ServiceInput, service::service_fn},
1130 rama_net::{http::TargetHttpVersion, tls::ApplicationProtocol},
1131 rama_tls::{
1132 ProtocolVersion,
1133 client::{NegotiatedTlsParameters, TlsServerAuthentication},
1134 },
1135 std::convert::Infallible,
1136 };
1137
1138 fn assert_future_budget(connector: &impl Service<Request>, name: &str) {
1139 let request = Request::builder()
1140 .uri("https://example.com/")
1141 .body(Body::empty())
1142 .unwrap();
1143 let future = connector.serve(request);
1144 let size = std::mem::size_of_val(&future);
1145 assert!(
1146 size <= kib(64),
1147 "{name} connector future is {size} bytes; connector error adapters must not duplicate nested future storage"
1148 );
1149 }
1150
1151 #[tokio::test]
1152 async fn default_connector_futures_stay_within_stack_budget() {
1153 let builder = || {
1154 EasyHttpConnectorBuilder::new()
1155 .with_default_transport_connector()
1156 .without_dns_connector()
1157 .without_tls_proxy_support()
1158 .without_proxy_support()
1159 };
1160 let plain = builder()
1161 .without_tls_support()
1162 .with_default_http_connector::<Body>(Executor::default())
1163 .with_default_connection_pool()
1164 .build_connector();
1165 assert_future_budget(&plain, "plain pooled");
1166 #[cfg(feature = "rustls")]
1167 {
1168 let pooled = builder()
1169 .with_tls_support_using_rustls(TlsClientConfig::new())
1170 .with_default_http_connector::<Body>(Executor::default())
1171 .with_default_connection_pool()
1172 .build_connector();
1173 assert_future_budget(&pooled, "rustls pooled");
1174 let unpooled = builder()
1175 .with_tls_support_using_rustls(TlsClientConfig::new())
1176 .with_default_http_connector::<Body>(Executor::default())
1177 .without_connection_pool()
1178 .build_connector();
1179 assert_future_budget(&unpooled, "rustls unpooled");
1180 }
1181 }
1182
1183 #[cfg(feature = "tls")]
1184 #[tokio::test]
1185 async fn custom_http_connector_discovers_h3_without_capability_configuration() {
1186 let cache = AltSvcCache::default();
1187 let origin =
1188 HttpOrigin::new(Protocol::HTTPS, HostWithPort::example_domain_https()).unwrap();
1189 let mut headers = HeaderMap::new();
1190 headers.insert(ALT_SVC, "h3=\":8443\"".parse().unwrap());
1191 cache.record(&origin, &headers, Duration::ZERO);
1192
1193 let builder = EasyHttpConnectorBuilder::new()
1194 .with_custom_transport_connector(())
1195 .without_dns_connector()
1196 .without_tls_proxy_support()
1197 .without_proxy_support()
1198 .without_tls_support()
1199 .with_custom_http_connector(layer_fn(|()| {
1200 service_fn(|input: ConnectRequest| async move {
1201 let version = input
1202 .extensions()
1203 .get_ref::<TargetHttpVersion>()
1204 .map_or(Version::HTTP_2, |version| version.0);
1205 let conn = ServiceInput::new(());
1206 conn.extensions.insert(TargetHttpVersion(version));
1207 conn.extensions
1208 .insert(TlsServerAuthentication(Some(input.authority.host.clone())));
1209 conn.extensions.insert(NegotiatedTlsParameters {
1210 protocol_version: ProtocolVersion::TLSv1_3,
1211 application_layer_protocol: Some(if version == Version::HTTP_3 {
1212 ApplicationProtocol::HTTP_3
1213 } else {
1214 ApplicationProtocol::HTTP_2
1215 }),
1216 peer_certificate_chain: None,
1217 server_name: None,
1218 resumed: None,
1219 });
1220 Ok::<_, Infallible>(EstablishedClientConnection { input, conn })
1221 })
1222 }))
1223 .without_connection_pool()
1224 .with_alt_svc_cache(cache.clone());
1225 let connector = builder.build_connector();
1226 for _ in 0..2 {
1227 let established = connector
1228 .serve(
1229 Request::builder()
1230 .uri("https://example.com/")
1231 .body(Body::empty())
1232 .unwrap(),
1233 )
1234 .await
1235 .unwrap();
1236 assert!(
1237 established
1238 .input
1239 .extensions()
1240 .contains::<SelectedHttpService>()
1241 );
1242 assert_eq!(established.input.version(), Version::HTTP_3);
1243 assert_eq!(
1244 established
1245 .input
1246 .extensions()
1247 .get_ref::<SelectedHttpService>()
1248 .unwrap()
1249 .candidate
1250 .target
1251 .port,
1252 8443
1253 );
1254 }
1255 }
1256
1257 #[test]
1258 fn custom_tls_proxy_layer_requires_no_builtin_provider() {
1259 EasyHttpConnectorBuilder::new()
1260 .with_custom_transport_connector(())
1261 .without_dns_connector()
1262 .with_custom_tls_proxy_connector(layer_fn(|inner| inner))
1263 .build_connector();
1264 }
1265
1266 #[test]
1267 fn custom_fixed_policy_pool_respects_explicit_override_identity() {
1268 let identifier = HttpConnIdentifier::default();
1269 let input = ConnectRequest::new(HostWithPort::example_domain_https());
1270 let fixed = identifier.id(&input).unwrap();
1271 assert!(fixed.is_reusable());
1272 let policy = TlsPoolId::builder()
1273 .with_verify(&TlsServerVerify(ServerVerifyMode::Disable))
1274 .build()
1275 .unwrap();
1276 input.extensions.insert(policy);
1277 let overridden = identifier.id(&input).unwrap();
1278 assert!(overridden.is_reusable());
1279 assert_ne!(fixed, overridden);
1280 assert_eq!(overridden, identifier.id(&input).unwrap());
1281 input.extensions.insert(TlsPoolId::non_reusable());
1282 assert!(!identifier.id(&input).unwrap().is_reusable());
1283 }
1284
1285 #[cfg(any(
1286 feature = "boring",
1287 all(feature = "rustls", any(feature = "ring", feature = "aws-lc"))
1288 ))]
1289 #[derive(Clone)]
1290 struct RecordingResolver {
1291 calls: Arc<AtomicUsize>,
1292 loopback: bool,
1293 }
1294
1295 #[cfg(any(
1296 feature = "boring",
1297 all(feature = "rustls", any(feature = "ring", feature = "aws-lc"))
1298 ))]
1299 impl DnsAddressResolver for RecordingResolver {
1300 type Error = Infallible;
1301
1302 fn lookup_ipv4(
1303 &self,
1304 domain: Domain,
1305 ) -> impl Stream<Item = Result<Ipv4Addr, Self::Error>> + Send + '_ {
1306 assert_eq!(domain.as_str(), "private.invalid");
1307 self.calls.fetch_add(1, Ordering::Relaxed);
1308 stream::iter(self.loopback.then_some(Ok(Ipv4Addr::LOCALHOST)))
1309 }
1310
1311 fn lookup_ipv6(
1312 &self,
1313 domain: Domain,
1314 ) -> impl Stream<Item = Result<Ipv6Addr, Self::Error>> + Send + '_ {
1315 assert_eq!(domain.as_str(), "private.invalid");
1316 self.calls.fetch_add(1, Ordering::Relaxed);
1317 stream::iter(self.loopback.then_some(Ok(Ipv6Addr::LOCALHOST)))
1318 }
1319 }
1320
1321 #[cfg(any(
1322 feature = "boring",
1323 all(feature = "rustls", any(feature = "ring", feature = "aws-lc"))
1324 ))]
1325 struct RecordingDnsLayer {
1326 resolver: RecordingResolver,
1327 applied: Arc<AtomicUsize>,
1328 }
1329
1330 #[cfg(any(
1331 feature = "boring",
1332 all(feature = "rustls", any(feature = "ring", feature = "aws-lc"))
1333 ))]
1334 impl<S> Layer<S> for RecordingDnsLayer {
1335 type Service = DnsConnector<S, RecordingResolver>;
1336
1337 fn layer(&self, inner: S) -> Self::Service {
1338 self.applied.fetch_add(1, Ordering::Relaxed);
1339 DnsConnector::with_resolver(inner, self.resolver.clone())
1340 }
1341 }
1342
1343 #[cfg(any(
1344 feature = "boring",
1345 all(feature = "rustls", any(feature = "ring", feature = "aws-lc"))
1346 ))]
1347 #[tokio::test]
1348 async fn built_in_quic_uses_the_configured_dns_resolver() {
1349 let calls = Arc::new(AtomicUsize::new(0));
1350 let applied = Arc::new(AtomicUsize::new(0));
1351 let executor = Executor::default();
1352 let h3 = Http3Connector::builder(executor.clone())
1353 .build()
1354 .await
1355 .unwrap();
1356 let connector = EasyHttpConnectorBuilder::new()
1357 .with_default_transport_connector()
1358 .with_dns_connector(RecordingDnsLayer {
1359 resolver: RecordingResolver {
1360 calls: calls.clone(),
1361 loopback: false,
1362 },
1363 applied: applied.clone(),
1364 })
1365 .without_tls_proxy_support()
1366 .without_proxy_support()
1367 .without_tls_support()
1368 .with_default_http_connector::<Body>(executor)
1369 .with_http3_support(h3)
1370 .build_connector();
1371 let input = ConnectRequest::new("private.invalid:443".parse().unwrap())
1372 .with_application_protocol(Protocol::HTTPS);
1373 input.extensions.insert(RequestedVersion(Version::HTTP_3));
1374 let error = connector.serve(input).await.err().unwrap();
1375 assert_eq!(error.kind(), ConnectionErrorKind::Unavailable);
1376 assert!(calls.load(Ordering::Relaxed) > 0);
1377 assert_eq!(
1378 applied.load(Ordering::Relaxed),
1379 2,
1380 "the same DNS layer must wrap both transports"
1381 );
1382 }
1383
1384 #[cfg(any(
1385 feature = "boring",
1386 all(feature = "rustls", any(feature = "ring", feature = "aws-lc"))
1387 ))]
1388 #[tokio::test]
1389 async fn ipv4_quic_endpoint_skips_ipv6_candidates_without_terminal_failure() {
1390 let executor = Executor::default();
1391 let endpoint = Endpoint::bind_client(executor.clone(), SocketAddress::local_ipv4(0))
1392 .await
1393 .unwrap();
1394 let blackhole = tokio::net::UdpSocket::bind(SocketAddr::from(SocketAddress::local_ipv4(0)))
1395 .await
1396 .unwrap();
1397 let connector = Http3Connector::builder(executor)
1398 .with_endpoint(endpoint.clone())
1399 .build()
1400 .await
1401 .unwrap();
1402 let calls = Arc::new(AtomicUsize::new(0));
1403 let connector = DnsConnector::with_resolver(
1404 connector,
1405 RecordingResolver {
1406 calls: calls.clone(),
1407 loopback: true,
1408 },
1409 );
1410 let input = ConnectRequest::new(
1411 format!("private.invalid:{}", blackhole.local_addr().unwrap().port())
1412 .parse()
1413 .unwrap(),
1414 )
1415 .with_application_protocol(Protocol::HTTPS);
1416 let attempt = Arc::new(HttpServiceAttempt::default());
1417 input.extensions.insert_arc(attempt.clone());
1418 tokio::time::timeout(Duration::from_millis(500), connector.serve(input))
1419 .await
1420 .unwrap_err();
1421 assert!(calls.load(Ordering::Relaxed) >= 2);
1422 assert!(
1423 !attempt.failed(),
1424 "a mismatched candidate family must not turn the IPv4 timeout into an authentication failure"
1425 );
1426 endpoint.close(0u32, b"test complete");
1427 }
1428}