rama/http/client/
builder.rs

1use super::HttpConnector;
2use crate::{
3    Layer, Service,
4    dns::DnsResolver,
5    error::{BoxError, OpaqueError},
6    extensions::ExtensionsMut,
7    http::{Request, StreamingBody, client::proxy::layer::HttpProxyConnector},
8    net::client::{
9        EstablishedClientConnection,
10        pool::{
11            LruDropPool, PooledConnector,
12            http::{BasicHttpConId, BasicHttpConnIdentifier, HttpPooledConnectorConfig},
13        },
14    },
15    tcp::client::service::TcpConnector,
16};
17use std::{marker::PhantomData, time::Duration};
18
19#[cfg(feature = "boring")]
20use crate::tls::boring::client as boring_client;
21
22#[cfg(feature = "rustls")]
23use crate::tls::rustls::client as rustls_client;
24
25#[cfg(any(feature = "rustls", feature = "boring"))]
26use crate::http::layer::version_adapter::RequestVersionAdapter;
27
28#[cfg(feature = "socks5")]
29use crate::{http::client::proxy_connector::ProxyConnector, proxy::socks5::Socks5ProxyConnector};
30
31/// Builder that is designed to easily create a connoector for [`super::EasyHttpWebClient`] from most basic use cases
32#[derive(Default)]
33pub struct EasyHttpConnectorBuilder<C = (), S = ()> {
34    connector: C,
35    _phantom: PhantomData<S>,
36}
37
38#[non_exhaustive]
39#[derive(Debug)]
40pub struct TransportStage;
41#[non_exhaustive]
42#[derive(Debug)]
43pub struct ProxyTunnelStage;
44#[non_exhaustive]
45#[derive(Debug)]
46pub struct ProxyStage;
47#[non_exhaustive]
48#[derive(Debug)]
49pub struct TlsStage;
50#[non_exhaustive]
51#[derive(Debug)]
52pub struct HttpStage;
53#[non_exhaustive]
54#[derive(Debug)]
55pub struct PoolStage;
56
57impl EasyHttpConnectorBuilder {
58    #[must_use]
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    #[must_use]
64    pub fn with_default_transport_connector(
65        self,
66    ) -> EasyHttpConnectorBuilder<TcpConnector, TransportStage> {
67        let connector = TcpConnector::default();
68        EasyHttpConnectorBuilder {
69            connector,
70            _phantom: PhantomData,
71        }
72    }
73
74    /// Add a custom transport connector that will be used by this client for the transport layer
75    pub fn with_custom_transport_connector<C>(
76        self,
77        connector: C,
78    ) -> EasyHttpConnectorBuilder<C, TransportStage> {
79        EasyHttpConnectorBuilder {
80            connector,
81            _phantom: PhantomData,
82        }
83    }
84}
85
86impl<T, Stage> EasyHttpConnectorBuilder<T, Stage> {
87    /// Add a custom connector to this Stage.
88    ///
89    /// Adding a custom connector to a stage will not change the state
90    /// so this can be used to modify behaviour at a specific stage.
91    pub fn with_custom_connector<L>(
92        self,
93        connector_layer: L,
94    ) -> EasyHttpConnectorBuilder<L::Service, Stage>
95    where
96        L: Layer<T>,
97    {
98        let connector = connector_layer.into_layer(self.connector);
99
100        EasyHttpConnectorBuilder {
101            connector,
102            _phantom: PhantomData,
103        }
104    }
105}
106
107impl EasyHttpConnectorBuilder<TcpConnector, TransportStage> {
108    /// Add a custom [`DnsResolver`] that will be used by this client
109    pub fn with_dns_resolver<T: DnsResolver + Clone>(
110        self,
111        resolver: T,
112    ) -> EasyHttpConnectorBuilder<TcpConnector<T>, TransportStage> {
113        let connector = self.connector.with_dns(resolver);
114        EasyHttpConnectorBuilder {
115            connector,
116            _phantom: PhantomData,
117        }
118    }
119}
120
121impl<T> EasyHttpConnectorBuilder<T, TransportStage> {
122    #[cfg(any(feature = "rustls", feature = "boring"))]
123    /// Add a custom proxy tls connector that will be used to setup a tls connection to the proxy
124    pub fn with_custom_tls_proxy_connector<L>(
125        self,
126        connector_layer: L,
127    ) -> EasyHttpConnectorBuilder<L::Service, ProxyTunnelStage>
128    where
129        L: Layer<T>,
130    {
131        let connector = connector_layer.into_layer(self.connector);
132        EasyHttpConnectorBuilder {
133            connector,
134            _phantom: PhantomData,
135        }
136    }
137
138    #[cfg(feature = "boring")]
139    #[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
140    /// Support a tls tunnel to the proxy itself using boringssl
141    ///
142    /// Note that a tls proxy is not needed to make a https connection
143    /// to the final target. It only has an influence on the initial connection
144    /// to the proxy itself
145    pub fn with_tls_proxy_support_using_boringssl(
146        self,
147    ) -> EasyHttpConnectorBuilder<
148        boring_client::TlsConnector<T, boring_client::ConnectorKindTunnel>,
149        ProxyTunnelStage,
150    > {
151        let connector = boring_client::TlsConnector::tunnel(self.connector, None);
152        EasyHttpConnectorBuilder {
153            connector,
154            _phantom: PhantomData,
155        }
156    }
157
158    #[cfg(feature = "boring")]
159    #[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
160    /// Support a tls tunnel to the proxy itself using boringssl and the provided config
161    ///
162    /// Note that a tls proxy is not needed to make a https connection
163    /// to the final target. It only has an influence on the initial connection
164    /// to the proxy itself
165    pub fn with_tls_proxy_support_using_boringssl_config(
166        self,
167        config: std::sync::Arc<boring_client::TlsConnectorDataBuilder>,
168    ) -> EasyHttpConnectorBuilder<
169        boring_client::TlsConnector<T, boring_client::ConnectorKindTunnel>,
170        ProxyTunnelStage,
171    > {
172        let connector =
173            boring_client::TlsConnector::tunnel(self.connector, None).with_connector_data(config);
174        EasyHttpConnectorBuilder {
175            connector,
176            _phantom: PhantomData,
177        }
178    }
179
180    #[cfg(feature = "rustls")]
181    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
182    /// Support a tls tunnel to the proxy itself using rustls
183    ///
184    /// Note that a tls proxy is not needed to make a https connection
185    /// to the final target. It only has an influence on the initial connection
186    /// to the proxy itself
187    pub fn with_tls_proxy_support_using_rustls(
188        self,
189    ) -> EasyHttpConnectorBuilder<
190        rustls_client::TlsConnector<T, rustls_client::ConnectorKindTunnel>,
191        ProxyTunnelStage,
192    > {
193        let connector = rustls_client::TlsConnector::tunnel(self.connector, None);
194
195        EasyHttpConnectorBuilder {
196            connector,
197            _phantom: PhantomData,
198        }
199    }
200
201    #[cfg(feature = "rustls")]
202    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
203    /// Support a tls tunnel to the proxy itself using rustls and the provided config
204    ///
205    /// Note that a tls proxy is not needed to make a https connection
206    /// to the final target. It only has an influence on the initial connection
207    /// to the proxy itself
208    pub fn with_tls_proxy_support_using_rustls_config(
209        self,
210        config: rustls_client::TlsConnectorData,
211    ) -> EasyHttpConnectorBuilder<
212        rustls_client::TlsConnector<T, rustls_client::ConnectorKindTunnel>,
213        ProxyTunnelStage,
214    > {
215        let connector =
216            rustls_client::TlsConnector::tunnel(self.connector, None).with_connector_data(config);
217
218        EasyHttpConnectorBuilder {
219            connector,
220            _phantom: PhantomData,
221        }
222    }
223
224    /// Don't support a tls tunnel to the proxy itself
225    ///
226    /// Note that a tls proxy is not needed to make a https connection
227    /// to the final target. It only has an influence on the initial connection
228    /// to the proxy itself
229    pub fn without_tls_proxy_support(self) -> EasyHttpConnectorBuilder<T, ProxyTunnelStage> {
230        EasyHttpConnectorBuilder {
231            connector: self.connector,
232            _phantom: PhantomData,
233        }
234    }
235}
236
237impl<T> EasyHttpConnectorBuilder<T, ProxyTunnelStage> {
238    /// Add a custom proxy connector that will be used by this client
239    pub fn with_custom_proxy_connector<L>(
240        self,
241        connector_layer: L,
242    ) -> EasyHttpConnectorBuilder<L::Service, ProxyStage>
243    where
244        L: Layer<T>,
245    {
246        let connector = connector_layer.into_layer(self.connector);
247        EasyHttpConnectorBuilder {
248            connector,
249            _phantom: PhantomData,
250        }
251    }
252
253    #[cfg(feature = "socks5")]
254    #[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
255    /// Add support for usage of a http(s) and socks5(h) [`ProxyAddress`] to this client
256    ///
257    /// Note that a tls proxy is not needed to make a https connection
258    /// to the final target. It only has an influence on the initial connection
259    /// to the proxy itself
260    ///
261    /// [`ProxyAddress`]: rama_net::address::ProxyAddress
262    pub fn with_proxy_support(
263        self,
264    ) -> EasyHttpConnectorBuilder<ProxyConnector<std::sync::Arc<T>>, ProxyStage> {
265        use rama_http_backend::client::proxy::layer::HttpProxyConnectorLayer;
266        use rama_socks5::Socks5ProxyConnectorLayer;
267
268        let connector = ProxyConnector::optional(
269            self.connector,
270            Socks5ProxyConnectorLayer::required(),
271            HttpProxyConnectorLayer::required(),
272        );
273
274        EasyHttpConnectorBuilder {
275            connector,
276            _phantom: PhantomData,
277        }
278    }
279
280    #[cfg(not(feature = "socks5"))]
281    /// Add support for usage of a http(s) [`ProxyAddress`] to this client
282    ///
283    /// Note that a tls proxy is not needed to make a https connection
284    /// to the final target. It only has an influence on the initial connection
285    /// to the proxy itself
286    ///
287    /// Note to also enable socks proxy support enable feature `socks5`
288    ///
289    /// [`ProxyAddress`]: rama_net::address::ProxyAddress
290    pub fn with_proxy_support(self) -> EasyHttpConnectorBuilder<HttpProxyConnector<T>, ProxyStage> {
291        self.with_http_proxy_support()
292    }
293
294    /// Add support for usage of a http(s) [`ProxyAddress`] to this client
295    ///
296    /// Note that a tls proxy is not needed to make a https connection
297    /// to the final target. It only has an influence on the initial connection
298    /// to the proxy itself
299    ///
300    /// [`ProxyAddress`]: rama_net::address::ProxyAddress
301    pub fn with_http_proxy_support(
302        self,
303    ) -> EasyHttpConnectorBuilder<HttpProxyConnector<T>, ProxyStage> {
304        let connector = HttpProxyConnector::optional(self.connector);
305
306        EasyHttpConnectorBuilder {
307            connector,
308            _phantom: PhantomData,
309        }
310    }
311
312    #[cfg(feature = "socks5")]
313    #[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
314    /// Add support for usage of a socks5(h) [`ProxyAddress`] to this client
315    ///
316    /// [`ProxyAddress`]: rama_net::address::ProxyAddress
317    pub fn with_socks5_proxy_support(
318        self,
319    ) -> EasyHttpConnectorBuilder<Socks5ProxyConnector<T>, ProxyStage> {
320        let connector = Socks5ProxyConnector::optional(self.connector);
321
322        EasyHttpConnectorBuilder {
323            connector,
324            _phantom: PhantomData,
325        }
326    }
327
328    /// Make a client without proxy support
329    pub fn without_proxy_support(self) -> EasyHttpConnectorBuilder<T, ProxyStage> {
330        EasyHttpConnectorBuilder {
331            connector: self.connector,
332            _phantom: PhantomData,
333        }
334    }
335}
336
337impl<T> EasyHttpConnectorBuilder<T, ProxyStage> {
338    #[cfg(any(feature = "rustls", feature = "boring"))]
339    /// Add a custom tls connector that will be used by the client
340    ///
341    /// Note: when using a tls_connector you probably want to also
342    /// add a [`RequestVersionAdapter`] which applies the negotiated
343    /// http version from tls alpn. This can be achieved by using
344    /// [`Self::with_custom_connector`] just after adding the tls connector.
345    pub fn with_custom_tls_connector<L>(
346        self,
347        connector_layer: L,
348    ) -> EasyHttpConnectorBuilder<L::Service, TlsStage>
349    where
350        L: Layer<T>,
351    {
352        let connector = connector_layer.into_layer(self.connector);
353
354        EasyHttpConnectorBuilder {
355            connector,
356            _phantom: PhantomData,
357        }
358    }
359
360    #[cfg(feature = "boring")]
361    #[cfg_attr(docsrs, doc(cfg(feature = "boring")))]
362    /// Support https connections by using boringssl for tls
363    ///
364    /// Note: this also adds a [`RequestVersionAdapter`] to automatically change the
365    /// request version to the one configured with tls alpn. If this is not
366    /// wanted, use [`Self::with_custom_tls_connector`] instead.
367    pub fn with_tls_support_using_boringssl(
368        self,
369        config: Option<std::sync::Arc<boring_client::TlsConnectorDataBuilder>>,
370    ) -> EasyHttpConnectorBuilder<RequestVersionAdapter<boring_client::TlsConnector<T>>, TlsStage>
371    {
372        let connector =
373            boring_client::TlsConnector::auto(self.connector).maybe_with_connector_data(config);
374        let connector = RequestVersionAdapter::new(connector);
375
376        EasyHttpConnectorBuilder {
377            connector,
378            _phantom: PhantomData,
379        }
380    }
381
382    #[cfg(feature = "rustls")]
383    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
384    /// Support https connections by using ruslts for tls
385    ///
386    /// Note: this also adds a [`RequestVersionAdapter`] to automatically change the
387    /// request version to the one configured with tls alpn. If this is not
388    /// wanted, use [`Self::with_custom_tls_connector`] instead.
389    pub fn with_tls_support_using_rustls(
390        self,
391        config: Option<rustls_client::TlsConnectorData>,
392    ) -> EasyHttpConnectorBuilder<RequestVersionAdapter<rustls_client::TlsConnector<T>>, TlsStage>
393    {
394        let connector =
395            rustls_client::TlsConnector::auto(self.connector).maybe_with_connector_data(config);
396        let connector = RequestVersionAdapter::new(connector);
397
398        EasyHttpConnectorBuilder {
399            connector,
400            _phantom: PhantomData,
401        }
402    }
403
404    /// Dont support https on this connector
405    pub fn without_tls_support(self) -> EasyHttpConnectorBuilder<T, TlsStage> {
406        EasyHttpConnectorBuilder {
407            connector: self.connector,
408            _phantom: PhantomData,
409        }
410    }
411}
412
413impl<T> EasyHttpConnectorBuilder<T, TlsStage> {
414    /// Add http support to this connector
415    pub fn with_default_http_connector<Body>(
416        self,
417    ) -> EasyHttpConnectorBuilder<HttpConnector<T, Body>, HttpStage> {
418        let connector = HttpConnector::new(self.connector);
419
420        EasyHttpConnectorBuilder {
421            connector,
422            _phantom: PhantomData,
423        }
424    }
425
426    /// Add a custom http connector that will be run just after tls
427    pub fn with_custom_http_connector<L>(
428        self,
429        connector_layer: L,
430    ) -> EasyHttpConnectorBuilder<L::Service, HttpStage>
431    where
432        L: Layer<T>,
433    {
434        let connector = connector_layer.into_layer(self.connector);
435
436        EasyHttpConnectorBuilder {
437            connector,
438            _phantom: PhantomData,
439        }
440    }
441}
442
443type DefaultConnectionPoolBuilder<T, C> = EasyHttpConnectorBuilder<
444    PooledConnector<T, LruDropPool<C, BasicHttpConId>, BasicHttpConnIdentifier>,
445    PoolStage,
446>;
447
448impl<T> EasyHttpConnectorBuilder<T, HttpStage> {
449    /// Use the default connection pool for this [`super::EasyHttpWebClient`]
450    ///
451    /// This will create a [`LruDropPool`] using the provided limits
452    /// and will use [`BasicHttpConnIdentifier`] to group connection on protocol
453    /// and authority, which should cover most common use cases
454    ///
455    /// Use `wait_for_pool_timeout` to limit how long we wait for the pool to give us a connection
456    ///
457    /// If you need a different pool or custom way to group connection you can
458    /// use [`EasyHttpConnectorBuilder::with_custom_connection_pool()`] to provide
459    /// you own.
460    pub fn try_with_connection_pool<C>(
461        self,
462        config: HttpPooledConnectorConfig,
463    ) -> Result<DefaultConnectionPoolBuilder<T, C>, OpaqueError> {
464        let connector = config.build_connector(self.connector)?;
465
466        Ok(EasyHttpConnectorBuilder {
467            connector,
468            _phantom: PhantomData,
469        })
470    }
471
472    #[inline(always)]
473    /// Same as [`Self::try_with_connection_pool`] but using the default [`HttpPooledConnectorConfig`].
474    pub fn try_with_default_connection_pool<C>(
475        self,
476    ) -> Result<DefaultConnectionPoolBuilder<T, C>, OpaqueError> {
477        self.try_with_connection_pool(Default::default())
478    }
479
480    /// Configure this client to use the provided [`Pool`] and [`ReqToConnId`]
481    ///
482    /// Use `wait_for_pool_timeout` to limit how long we wait for the pool to give us a connection
483    ///
484    /// [`Pool`]: rama_net::client::pool::Pool
485    /// [`ReqToConnId`]: rama_net::client::pool::ReqToConnID
486    pub fn with_custom_connection_pool<P, R>(
487        self,
488        pool: P,
489        req_to_conn_id: R,
490        wait_for_pool_timeout: Option<Duration>,
491    ) -> EasyHttpConnectorBuilder<PooledConnector<T, P, R>, PoolStage> {
492        let connector = PooledConnector::new(self.connector, pool, req_to_conn_id)
493            .maybe_with_wait_for_pool_timeout(wait_for_pool_timeout);
494
495        EasyHttpConnectorBuilder {
496            connector,
497            _phantom: PhantomData,
498        }
499    }
500}
501
502impl<T, S> EasyHttpConnectorBuilder<T, S> {
503    /// Build a [`super::EasyHttpWebClient`] using the currently configured connector
504    pub fn build_client<Body, ModifiedBody, ConnResponse>(
505        self,
506    ) -> super::EasyHttpWebClient<Body, T::Output, ()>
507    where
508        Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
509        ModifiedBody:
510            StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
511        T: Service<
512                Request<Body>,
513                Output = EstablishedClientConnection<ConnResponse, Request<ModifiedBody>>,
514                Error = BoxError,
515            >,
516        ConnResponse: ExtensionsMut,
517    {
518        super::EasyHttpWebClient::new(self.connector.boxed())
519    }
520
521    /// Build a connector from the currently configured setup
522    pub fn build_connector(self) -> T {
523        self.connector
524    }
525}