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