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 [`super::EasyHttpWebClient`] from most basic use cases
32#[derive(Default)]
33pub struct EasyHttpWebClientBuilder<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 EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<TcpConnector, TransportStage> {
67        let connector = TcpConnector::default();
68        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<C, TransportStage> {
79        EasyHttpWebClientBuilder {
80            connector,
81            _phantom: PhantomData,
82        }
83    }
84}
85
86impl<T, Stage> EasyHttpWebClientBuilder<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    ) -> EasyHttpWebClientBuilder<L::Service, Stage>
95    where
96        L: Layer<T>,
97    {
98        let connector = connector_layer.into_layer(self.connector);
99
100        EasyHttpWebClientBuilder {
101            connector,
102            _phantom: PhantomData,
103        }
104    }
105}
106
107impl EasyHttpWebClientBuilder<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    ) -> EasyHttpWebClientBuilder<TcpConnector<T>, TransportStage> {
113        let connector = self.connector.with_dns(resolver);
114        EasyHttpWebClientBuilder {
115            connector,
116            _phantom: PhantomData,
117        }
118    }
119}
120
121impl<T> EasyHttpWebClientBuilder<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    ) -> EasyHttpWebClientBuilder<L::Service, ProxyTunnelStage>
128    where
129        L: Layer<T>,
130    {
131        let connector = connector_layer.into_layer(self.connector);
132        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<
148        boring_client::TlsConnector<T, boring_client::ConnectorKindTunnel>,
149        ProxyTunnelStage,
150    > {
151        let connector = boring_client::TlsConnector::tunnel(self.connector, None);
152        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<
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        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<
190        rustls_client::TlsConnector<T, rustls_client::ConnectorKindTunnel>,
191        ProxyTunnelStage,
192    > {
193        let connector = rustls_client::TlsConnector::tunnel(self.connector, None);
194
195        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<
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        EasyHttpWebClientBuilder {
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) -> EasyHttpWebClientBuilder<T, ProxyTunnelStage> {
230        EasyHttpWebClientBuilder {
231            connector: self.connector,
232            _phantom: PhantomData,
233        }
234    }
235}
236
237impl<T> EasyHttpWebClientBuilder<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    ) -> EasyHttpWebClientBuilder<L::Service, ProxyStage>
243    where
244        L: Layer<T>,
245    {
246        let connector = connector_layer.into_layer(self.connector);
247        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<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        EasyHttpWebClientBuilder {
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) -> EasyHttpWebClientBuilder<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    ) -> EasyHttpWebClientBuilder<HttpProxyConnector<T>, ProxyStage> {
304        let connector = HttpProxyConnector::optional(self.connector);
305
306        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<Socks5ProxyConnector<T>, ProxyStage> {
320        let connector = Socks5ProxyConnector::optional(self.connector);
321
322        EasyHttpWebClientBuilder {
323            connector,
324            _phantom: PhantomData,
325        }
326    }
327
328    /// Make a client without proxy support
329    pub fn without_proxy_support(self) -> EasyHttpWebClientBuilder<T, ProxyStage> {
330        EasyHttpWebClientBuilder {
331            connector: self.connector,
332            _phantom: PhantomData,
333        }
334    }
335}
336
337impl<T> EasyHttpWebClientBuilder<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    ) -> EasyHttpWebClientBuilder<L::Service, TlsStage>
349    where
350        L: Layer<T>,
351    {
352        let connector = connector_layer.into_layer(self.connector);
353
354        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<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        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<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        EasyHttpWebClientBuilder {
399            connector,
400            _phantom: PhantomData,
401        }
402    }
403
404    /// Dont support https on this connector
405    pub fn without_tls_support(self) -> EasyHttpWebClientBuilder<T, TlsStage> {
406        EasyHttpWebClientBuilder {
407            connector: self.connector,
408            _phantom: PhantomData,
409        }
410    }
411}
412
413impl<T> EasyHttpWebClientBuilder<T, TlsStage> {
414    /// Add http support to this connector
415    pub fn with_default_http_connector(
416        self,
417    ) -> EasyHttpWebClientBuilder<HttpConnector<T>, HttpStage> {
418        let connector = HttpConnector::new(self.connector);
419
420        EasyHttpWebClientBuilder {
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    ) -> EasyHttpWebClientBuilder<L::Service, HttpStage>
431    where
432        L: Layer<T>,
433    {
434        let connector = connector_layer.into_layer(self.connector);
435
436        EasyHttpWebClientBuilder {
437            connector,
438            _phantom: PhantomData,
439        }
440    }
441}
442
443impl<T, I> EasyHttpWebClientBuilder<HttpConnector<T, I>, HttpStage> {
444    /// Add a http request inspector that will run just before doing the actual http request
445    pub fn with_svc_req_inspector<U>(
446        self,
447        http_req_inspector: U,
448    ) -> EasyHttpWebClientBuilder<HttpConnector<T, U>, HttpStage> {
449        EasyHttpWebClientBuilder {
450            connector: self.connector.with_svc_req_inspector(http_req_inspector),
451            _phantom: PhantomData,
452        }
453    }
454}
455
456type DefaultConnectionPoolBuilder<T, C> = EasyHttpWebClientBuilder<
457    PooledConnector<T, LruDropPool<C, BasicHttpConId>, BasicHttpConnIdentifier>,
458    PoolStage,
459>;
460
461impl<T> EasyHttpWebClientBuilder<T, HttpStage> {
462    /// Use the default connection pool for this [`super::EasyHttpWebClient`]
463    ///
464    /// This will create a [`LruDropPool`] using the provided limits
465    /// and will use [`BasicHttpConnIdentifier`] to group connection on protocol
466    /// and authority, which should cover most common use cases
467    ///
468    /// Use `wait_for_pool_timeout` to limit how long we wait for the pool to give us a connection
469    ///
470    /// If you need a different pool or custom way to group connection you can
471    /// use [`EasyHttpWebClientBuilder::with_custom_connection_pool()`] to provide
472    /// you own.
473    pub fn with_connection_pool<C>(
474        self,
475        config: HttpPooledConnectorConfig,
476    ) -> Result<DefaultConnectionPoolBuilder<T, C>, OpaqueError> {
477        let connector = config.build_connector(self.connector)?;
478
479        Ok(EasyHttpWebClientBuilder {
480            connector,
481            _phantom: PhantomData,
482        })
483    }
484
485    /// Configure this client to use the provided [`Pool`] and [`ReqToConnId`]
486    ///
487    /// Use `wait_for_pool_timeout` to limit how long we wait for the pool to give us a connection
488    ///
489    /// [`Pool`]: rama_net::client::pool::Pool
490    /// [`ReqToConnId`]: rama_net::client::pool::ReqToConnID
491    pub fn with_custom_connection_pool<P, R>(
492        self,
493        pool: P,
494        req_to_conn_id: R,
495        wait_for_pool_timeout: Option<Duration>,
496    ) -> EasyHttpWebClientBuilder<PooledConnector<T, P, R>, PoolStage> {
497        let connector = PooledConnector::new(self.connector, pool, req_to_conn_id)
498            .maybe_with_wait_for_pool_timeout(wait_for_pool_timeout);
499
500        EasyHttpWebClientBuilder {
501            connector,
502            _phantom: PhantomData,
503        }
504    }
505}
506
507impl<T, S> EasyHttpWebClientBuilder<T, S> {
508    /// Build a [`super::EasyHttpWebClient`] using the provided config
509    pub fn build<Body, ModifiedBody, ConnResponse>(
510        self,
511    ) -> super::EasyHttpWebClient<Body, T::Response>
512    where
513        Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
514        ModifiedBody:
515            StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
516        T: Service<
517                Request<Body>,
518                Response = EstablishedClientConnection<ConnResponse, Request<ModifiedBody>>,
519                Error = BoxError,
520            >,
521        ConnResponse: ExtensionsMut,
522    {
523        super::EasyHttpWebClient::new(self.connector.boxed())
524    }
525}