rama/http/client/
builder.rs

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