use std::time::Duration;
use http::Uri;
use hyper_util::client::legacy::{connect::Connect, Error as LegacyHyperError};
#[cfg(all(feature = "aws-lc-rs", feature = "hyper-rustls", not(feature = "ring")))]
use rustls::crypto::aws_lc_rs::default_provider as default_crypto_provider;
#[cfg(all(feature = "ring", feature = "hyper-rustls"))]
use rustls::crypto::ring::default_provider as default_crypto_provider;
#[cfg(all(
feature = "hyper-rustls",
not(any(feature = "ring", feature = "aws-lc-rs"))
))]
compile_error!(
"The `hyper-rustls` feature requires either the `ring` or `aws-lc-rs` feature to be enabled"
);
use thiserror::Error as ThisError;
use crate::Error;
type HyperResponse = http::Response<hyper::body::Incoming>;
pub(crate) type LegacyClient<C> = hyper_util::client::legacy::Client<C, String>;
#[derive(Debug, ThisError)]
pub enum SendError {
#[error("Request timed out")]
Timeout,
#[error("Hyper error: {0}")]
Hyper(#[source] LegacyHyperError),
}
pub trait HyperClientBuilder {
type Connector: Connect + Clone + Send + Sync + 'static;
fn with_timeout(self, timeout: Duration) -> Self;
fn build_hyper_client(self) -> Result<HttpClient<Self::Connector>, Error>;
}
#[derive(Clone)]
pub struct HttpClient<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
client: LegacyClient<C>,
timeout: Option<Duration>,
}
impl<C> HttpClient<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
pub(crate) fn new(hyper_client: LegacyClient<C>, timeout: Option<Duration>) -> Self {
Self {
client: hyper_client,
timeout,
}
}
pub(crate) fn set_timeout(&mut self, timeout: Duration) {
self.timeout = Some(timeout);
}
#[doc(hidden)]
pub async fn get(&self, uri: Uri) -> Result<HyperResponse, hyper_util::client::legacy::Error> {
self.client.get(uri).await
}
}
impl<C> HyperClientBuilder for HttpClient<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
type Connector = C;
fn with_timeout(mut self, timeout: Duration) -> Self {
self.set_timeout(timeout);
self
}
fn build_hyper_client(self) -> Result<HttpClient<Self::Connector>, Error> {
Ok(self)
}
}
impl<C> SendRequest for HttpClient<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
async fn request(&self, payload: http::Request<String>) -> Result<HyperResponse, SendError> {
let future = self.client.request(payload);
match self.timeout {
Some(duration) => tokio::time::timeout(duration, future)
.await
.map_err(|_| SendError::Timeout)?,
None => future.await,
}
.map_err(SendError::Hyper)
}
}
pub(crate) trait SendRequest {
async fn request(&self, payload: http::Request<String>) -> Result<HyperResponse, SendError>;
}
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
#[derive(Default)]
pub struct DefaultHyperClientBuilder {
timeout: Option<Duration>,
}
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
impl DefaultHyperClientBuilder {
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
}
#[cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "hyper-rustls", feature = "hyper-tls"))))]
impl HyperClientBuilder for DefaultHyperClientBuilder {
#[cfg(feature = "hyper-rustls")]
type Connector =
hyper_rustls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
type Connector = hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
fn build_hyper_client(self) -> Result<HttpClient<Self::Connector>, Error> {
#[cfg(feature = "hyper-rustls")]
let connector = hyper_rustls::HttpsConnectorBuilder::new()
.with_provider_and_native_roots(default_crypto_provider())?
.https_or_http()
.enable_http1()
.enable_http2()
.build();
#[cfg(all(not(feature = "hyper-rustls"), feature = "hyper-tls"))]
let connector = hyper_tls::HttpsConnector::new();
Ok(HttpClient::new(
hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
.pool_max_idle_per_host(0)
.build::<_, String>(connector),
self.timeout,
))
}
}
pub struct CustomHyperClientBuilder<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
client: HttpClient<C>,
timeout: Option<Duration>,
}
impl<C> From<LegacyClient<C>> for CustomHyperClientBuilder<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
fn from(client: LegacyClient<C>) -> Self {
Self {
client: HttpClient::new(client, None),
timeout: None,
}
}
}
impl<C> HyperClientBuilder for CustomHyperClientBuilder<C>
where
C: Connect + Clone + Send + Sync + 'static,
{
type Connector = C;
fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
fn build_hyper_client(self) -> Result<HttpClient<Self::Connector>, Error> {
Ok(self.client)
}
}