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 =
100 rama_tls_boring::client::TlsConnectorDataBuilder::new_http_auto().into_shared_builder();
101
102 EasyHttpConnectorBuilder::new()
103 .with_default_transport_connector()
104 .with_tls_proxy_support_using_boringssl()
105 .with_proxy_support()
106 .with_tls_support_using_boringssl(Some(tls_config))
107 .with_default_http_connector(exec)
108 .build_client()
109 }
110
111 #[cfg(all(feature = "rustls", not(feature = "boring")))]
112 pub fn default_with_executor(exec: Executor) -> Self {
113 let tls_config = rama_tls_rustls::client::TlsConnectorData::try_new_http_auto()
114 .expect("connector data with http auto");
115
116 EasyHttpConnectorBuilder::new()
117 .with_default_transport_connector()
118 .with_tls_proxy_support_using_rustls()
119 .with_proxy_support()
120 .with_tls_support_using_rustls(Some(tls_config))
121 .with_default_http_connector(exec)
122 .build_client()
123 }
124
125 #[cfg(not(any(feature = "rustls", feature = "boring")))]
126 pub fn default_with_executor(exec: Executor) -> Self {
127 EasyHttpConnectorBuilder::new()
128 .with_default_transport_connector()
129 .without_tls_proxy_support()
130 .with_proxy_support()
131 .without_tls_support()
132 .with_default_http_connector(exec)
133 .build_client()
134 }
135}
136
137impl<BodyIn, ConnResponse> EasyHttpWebClient<BodyIn, ConnResponse, ()>
138where
139 BodyIn: Send + 'static,
140{
141 #[must_use]
143 pub fn new<S>(connector: S) -> Self
144 where
145 S: Service<Request<BodyIn>, Output = ConnResponse, Error: Into<BoxError>>,
146 {
147 Self {
148 connector: MapErr::into_opaque_error(connector).boxed(),
149 jit_layers: (),
150 }
151 }
152}
153
154impl<BodyIn, ConnResponse, L> EasyHttpWebClient<BodyIn, ConnResponse, L> {
155 #[must_use]
157 pub fn with_connector<S, BodyInNew, ConnResponseNew>(
158 self,
159 connector: S,
160 ) -> EasyHttpWebClient<BodyInNew, ConnResponseNew, L>
161 where
162 S: Service<Request<BodyInNew>, Output = ConnResponseNew, Error: Into<BoxError>>,
163 BodyInNew: Send + 'static,
164 {
165 EasyHttpWebClient {
166 connector: MapErr::into_opaque_error(connector).boxed(),
167 jit_layers: self.jit_layers,
168 }
169 }
170
171 pub fn with_jit_layer<T>(self, jit_layers: T) -> EasyHttpWebClient<BodyIn, ConnResponse, T> {
179 EasyHttpWebClient {
180 connector: self.connector,
181 jit_layers,
182 }
183 }
184}
185
186impl<Body, ConnectionBody, Connection, L> Service<Request<Body>>
187 for EasyHttpWebClient<Body, EstablishedClientConnection<Connection, Request<ConnectionBody>>, L>
188where
189 Body: StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
190 Connection:
191 Service<Request<ConnectionBody>, Output = Response, Error = BoxError> + ExtensionsRef,
192 ConnectionBody:
195 StreamingBody<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
196 L: Layer<
197 Connection,
198 Service: Service<Request<ConnectionBody>, Output = Response, Error = BoxError>,
199 > + Send
200 + Sync
201 + 'static,
202{
203 type Output = Response;
204 type Error = OpaqueError;
205
206 async fn serve(&self, req: Request<Body>) -> Result<Self::Output, Self::Error> {
207 let uri = req.uri().clone();
208
209 let EstablishedClientConnection {
210 input: req,
211 conn: http_connection,
212 } = self.connector.serve(req).await.into_opaque_error()?;
213
214 req.extensions()
215 .insert(Egress(http_connection.extensions().clone()));
216
217 let http_connection = self.jit_layers.layer(http_connection);
218
219 tracing::trace!(url.full = %uri, "send http req to connector stack");
221
222 let result = http_connection.serve(req).await;
223
224 match result {
225 Ok(resp) => {
226 tracing::trace!(url.full = %uri, "response received from connector stack");
227 Ok(resp)
228 }
229 Err(err) => Err(err
230 .context("http request failure")
231 .context_field("uri", uri)
232 .into_opaque_error()),
233 }
234 }
235}