rama/http/client/
builder.rs

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