1use std::fmt;
7
8use crate::{
9 Layer, Service,
10 error::BoxError,
11 extensions::ExtensionsRef,
12 http::{Request, Response, StreamingBody},
13 net::client::EstablishedClientConnection,
14 rt::Executor,
15 service::BoxService,
16 telemetry::tracing,
17};
18
19#[doc(inline)]
20pub use ::rama_http_backend::client::*;
21use rama_core::{
22 error::{ErrorContext, ErrorExt as _, extra::OpaqueError},
23 extensions::Egress,
24 layer::MapErr,
25};
26
27pub mod builder;
28#[doc(inline)]
29pub use builder::EasyHttpConnectorBuilder;
30
31#[cfg(feature = "socks5")]
32mod proxy_connector;
33#[cfg(feature = "socks5")]
34#[cfg_attr(docsrs, doc(cfg(feature = "socks5")))]
35#[doc(inline)]
36pub use proxy_connector::{MaybeProxiedConnection, ProxyConnector, ProxyConnectorLayer};
37
38pub struct EasyHttpWebClient<BodyIn, ConnResponse, L> {
50 connector: BoxService<Request<BodyIn>, ConnResponse, OpaqueError>,
51 jit_layers: L,
52}
53
54impl<BodyIn, ConnResponse, L> fmt::Debug for EasyHttpWebClient<BodyIn, ConnResponse, L> {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 f.debug_struct("EasyHttpWebClient").finish()
57 }
58}
59
60impl<BodyIn, ConnResponse, L: Clone> Clone for EasyHttpWebClient<BodyIn, ConnResponse, L> {
61 fn clone(&self) -> Self {
62 Self {
63 connector: self.connector.clone(),
64 jit_layers: self.jit_layers.clone(),
65 }
66 }
67}
68
69impl EasyHttpWebClient<(), (), ()> {
70 #[must_use]
72 pub fn connector_builder() -> EasyHttpConnectorBuilder {
73 EasyHttpConnectorBuilder::new()
74 }
75}
76
77impl<Body> Default
78 for EasyHttpWebClient<
79 Body,
80 EstablishedClientConnection<HttpClientService<Body>, Request<Body>>,
81 (),
82 >
83where
84 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
85{
86 #[inline(always)]
87 fn default() -> Self {
88 Self::default_with_executor(Executor::default())
89 }
90}
91
92impl<Body>
93 EasyHttpWebClient<Body, EstablishedClientConnection<HttpClientService<Body>, Request<Body>>, ()>
94where
95 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
96{
97 #[cfg(feature = "boring")]
98 pub fn default_with_executor(exec: Executor) -> Self {
99 let tls_config = crate::net::tls::client::TlsClientConfig::default_http();
100
101 EasyHttpConnectorBuilder::new()
102 .with_default_transport_connector()
103 .with_tls_proxy_support_using_boringssl()
104 .with_proxy_support()
105 .with_tls_support_using_boringssl(tls_config)
106 .with_default_http_connector(exec)
107 .build_client()
108 }
109
110 #[cfg(all(feature = "rustls", not(feature = "boring")))]
111 pub fn default_with_executor(exec: Executor) -> Self {
112 let tls_config = crate::net::tls::client::TlsClientConfig::default_http();
113
114 EasyHttpConnectorBuilder::new()
115 .with_default_transport_connector()
116 .with_tls_proxy_support_using_rustls()
117 .with_proxy_support()
118 .with_tls_support_using_rustls(tls_config)
119 .with_default_http_connector(exec)
120 .build_client()
121 }
122
123 #[cfg(not(any(feature = "rustls", feature = "boring")))]
124 pub fn default_with_executor(exec: Executor) -> Self {
125 EasyHttpConnectorBuilder::new()
126 .with_default_transport_connector()
127 .without_tls_proxy_support()
128 .with_proxy_support()
129 .without_tls_support()
130 .with_default_http_connector(exec)
131 .build_client()
132 }
133}
134
135impl<BodyIn, ConnResponse> EasyHttpWebClient<BodyIn, ConnResponse, ()>
136where
137 BodyIn: Send + 'static,
138{
139 #[must_use]
141 pub fn new<S>(connector: S) -> Self
142 where
143 S: Service<Request<BodyIn>, Output = ConnResponse, Error: Into<BoxError>>,
144 {
145 Self {
146 connector: MapErr::into_opaque_error(connector).boxed(),
147 jit_layers: (),
148 }
149 }
150}
151
152impl<BodyIn, ConnResponse, L> EasyHttpWebClient<BodyIn, ConnResponse, L> {
153 #[must_use]
155 pub fn with_connector<S, BodyInNew, ConnResponseNew>(
156 self,
157 connector: S,
158 ) -> EasyHttpWebClient<BodyInNew, ConnResponseNew, L>
159 where
160 S: Service<Request<BodyInNew>, Output = ConnResponseNew, Error: Into<BoxError>>,
161 BodyInNew: Send + 'static,
162 {
163 EasyHttpWebClient {
164 connector: MapErr::into_opaque_error(connector).boxed(),
165 jit_layers: self.jit_layers,
166 }
167 }
168
169 pub fn with_jit_layer<T>(self, jit_layers: T) -> EasyHttpWebClient<BodyIn, ConnResponse, T> {
177 EasyHttpWebClient {
178 connector: self.connector,
179 jit_layers,
180 }
181 }
182}
183
184impl<Body, ConnectionBody, Connection, L> Service<Request<Body>>
185 for EasyHttpWebClient<Body, EstablishedClientConnection<Connection, Request<ConnectionBody>>, L>
186where
187 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
188 Connection:
189 Service<Request<ConnectionBody>, Output = Response, Error = BoxError> + ExtensionsRef,
190 ConnectionBody:
193 StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
194 L: Layer<
195 Connection,
196 Service: Service<Request<ConnectionBody>, Output = Response, Error = BoxError>,
197 > + Send
198 + Sync
199 + 'static,
200{
201 type Output = Response;
202 type Error = OpaqueError;
203
204 async fn serve(&self, req: Request<Body>) -> Result<Self::Output, Self::Error> {
205 let uri = req.uri().clone();
206
207 let EstablishedClientConnection {
208 input: req,
209 conn: http_connection,
210 } = self.connector.serve(req).await.into_opaque_error()?;
211
212 req.extensions()
213 .insert(Egress(http_connection.extensions().clone()));
214
215 let http_connection = self.jit_layers.layer(http_connection);
216
217 tracing::trace!(url.full = %uri, "send http req to connector stack");
219
220 let result = http_connection.serve(req).await;
221
222 match result {
223 Ok(resp) => {
224 tracing::trace!(url.full = %uri, "response received from connector stack");
225 Ok(resp)
226 }
227 Err(err) => Err(err
228 .context("http request failure")
229 .context_field("uri", uri)
230 .into_opaque_error()),
231 }
232 }
233}