rama_http_backend/client/
mod.rsuse proxy::layer::HttpProxyConnector;
use rama_core::{
error::{BoxError, ErrorExt, OpaqueError},
Context, Service,
};
use rama_http_types::{dep::http_body, Request, Response};
use rama_net::client::{ConnectorService, EstablishedClientConnection};
use rama_tcp::client::service::TcpConnector;
#[cfg(any(feature = "rustls", feature = "boring"))]
use rama_tls::std::client::{TlsConnector, TlsConnectorData};
#[cfg(any(feature = "rustls", feature = "boring"))]
use rama_net::tls::client::ClientConfig;
#[cfg(any(feature = "rustls", feature = "boring"))]
use rama_core::error::ErrorContext;
mod svc;
#[doc(inline)]
pub use svc::HttpClientService;
mod conn;
#[doc(inline)]
pub use conn::{HttpConnector, HttpConnectorLayer};
use tracing::trace;
pub mod proxy;
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct HttpClient {
#[cfg(any(feature = "rustls", feature = "boring"))]
tls_config: Option<ClientConfig>,
#[cfg(any(feature = "rustls", feature = "boring"))]
proxy_tls_config: Option<ClientConfig>,
}
impl HttpClient {
pub fn new() -> Self {
Self::default()
}
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn set_tls_config(&mut self, cfg: ClientConfig) -> &mut Self {
self.tls_config = Some(cfg);
self
}
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn with_tls_config(mut self, cfg: ClientConfig) -> Self {
self.tls_config = Some(cfg);
self
}
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn maybe_with_tls_config(mut self, cfg: Option<ClientConfig>) -> Self {
self.tls_config = cfg;
self
}
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn set_proxy_tls_config(&mut self, cfg: ClientConfig) -> &mut Self {
self.proxy_tls_config = Some(cfg);
self
}
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn with_proxy_tls_config(mut self, cfg: ClientConfig) -> Self {
self.proxy_tls_config = Some(cfg);
self
}
#[cfg(any(feature = "rustls", feature = "boring"))]
pub fn maybe_proxy_with_tls_config(mut self, cfg: Option<ClientConfig>) -> Self {
self.proxy_tls_config = cfg;
self
}
}
impl<State, Body> Service<State, Request<Body>> for HttpClient
where
State: Clone + Send + Sync + 'static,
Body: http_body::Body<Data: Send + 'static, Error: Into<BoxError>> + Unpin + Send + 'static,
{
type Response = Response;
type Error = OpaqueError;
async fn serve(
&self,
ctx: Context<State>,
req: Request<Body>,
) -> Result<Self::Response, Self::Error> {
let uri = req.uri().clone();
let original_req_version = req.version();
let tcp_connector = TcpConnector::new();
#[cfg(any(feature = "rustls", feature = "boring"))]
let connector = {
let proxy_tls_connector_data = match &self.proxy_tls_config {
Some(proxy_tls_config) => {
trace!("create proxy tls connector using pre-defined rama tls client config");
proxy_tls_config
.clone()
.try_into()
.context("HttpClient: create proxy tls connector data from tls config")?
}
None => {
trace!("create proxy tls connector using the 'new_http_auto' constructor");
TlsConnectorData::new().context(
"HttpClient: create proxy tls connector data with no application presets",
)?
}
};
let transport_connector = HttpProxyConnector::optional(
TlsConnector::tunnel(tcp_connector, None)
.with_connector_data(proxy_tls_connector_data),
);
let tls_connector_data = match &self.tls_config {
Some(tls_config) => {
trace!("create tls connector using pre-defined rama tls client config");
tls_config
.clone()
.try_into()
.context("HttpClient: create tls connector data from tls config")?
}
None => {
trace!("create tls connector using the 'new_http_auto' constructor");
TlsConnectorData::new_http_auto()
.context("HttpClient: create tls connector data for http (auto)")?
}
};
HttpConnector::new(
TlsConnector::auto(transport_connector).with_connector_data(tls_connector_data),
)
};
#[cfg(not(any(feature = "rustls", feature = "boring")))]
let connector = HttpConnector::new(HttpProxyConnector::optional(tcp_connector));
let EstablishedClientConnection { ctx, req, conn, .. } = connector
.connect(ctx, req)
.await
.map_err(|err| OpaqueError::from_boxed(err).with_context(|| uri.to_string()))?;
trace!(uri = %uri, "send http req to connector stack");
let mut resp = conn.serve(ctx, req).await.map_err(|err| {
OpaqueError::from_boxed(err)
.with_context(|| format!("http request failure for uri: {uri}"))
})?;
trace!(uri = %uri, "response received from connector stack");
trace!(
"incoming response version {:?}, normalizing to {:?}",
resp.version(),
original_req_version
);
*resp.version_mut() = original_req_version;
Ok(resp)
}
}