use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use hyper::client::conn::{http1, http2};
use hyper_util::rt::{TokioExecutor, TokioTimer};
use opentelemetry::KeyValue;
use tower::BoxError;
use tower::Service;
use super::Origin;
use super::connect_with_timeout;
use super::h1::H1Connection;
use super::h2::H2Connection;
use crate::error::HttpClientError;
use crate::metrics::ConnectionMetrics;
#[derive(Clone)]
pub(crate) struct TcpConnect<S> {
inner: S,
timeout: Duration,
}
impl<S> TcpConnect<S> {
pub(crate) fn new(inner: S, timeout: Duration) -> Self {
Self { inner, timeout }
}
}
impl<S, IO> Service<Origin> for TcpConnect<S>
where
S: Service<http::Uri, Response = IO> + Clone + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<BoxError>,
{
type Response = IO;
type Error = BoxError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, origin: Origin) -> Self::Future {
let clone = self.inner.clone();
let inner = std::mem::replace(&mut self.inner, clone);
let timeout = self.timeout;
Box::pin(async move {
connect_with_timeout(inner, origin, timeout)
.await
.map_err(BoxError::from)
})
}
}
#[derive(Clone)]
pub(crate) struct FixedOrigin<S> {
pub(crate) inner: S,
pub(crate) origin: Origin,
}
impl<S> Service<()> for FixedOrigin<S>
where
S: Service<Origin> + Clone,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, (): ()) -> Self::Future {
self.inner.call(self.origin.clone())
}
}
#[derive(Clone)]
pub(crate) struct Http1Handshake<S> {
pub(crate) inner: S,
pub(crate) metrics: ConnectionMetrics,
pub(crate) attrs: Vec<KeyValue>,
}
impl<S> Http1Handshake<S> {
pub(crate) fn new(inner: S, metrics: ConnectionMetrics, attrs: Vec<KeyValue>) -> Self {
Self {
inner,
metrics,
attrs,
}
}
}
impl<S, IO> Service<Origin> for Http1Handshake<S>
where
S: Service<Origin, Response = IO, Error = BoxError> + Clone + Send + 'static,
S::Future: Send + 'static,
IO: hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static,
{
type Response = H1Connection;
type Error = BoxError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, origin: Origin) -> Self::Future {
let io_fut = self.inner.call(origin);
let metrics = self.metrics.clone();
let attrs = self.attrs.clone();
Box::pin(async move {
let io = io_fut.await?;
let (sender, conn) = http1::handshake(io)
.await
.map_err(|source| BoxError::from(HttpClientError::Handshake { source }))?;
tokio::spawn(async move {
if let Err(e) = conn.await {
log::debug!(error:% = e; "HTTP/1 connection error");
}
});
Ok(H1Connection::new(sender, &metrics, attrs))
})
}
}
#[derive(Clone)]
pub(crate) struct Http2Handshake<S> {
pub(crate) inner: S,
pub(crate) metrics: ConnectionMetrics,
pub(crate) attrs: Vec<KeyValue>,
pub(crate) keep_alive_interval: Duration,
pub(crate) keep_alive_timeout: Duration,
pub(crate) keep_alive_while_idle: bool,
}
impl<S, IO> Service<()> for Http2Handshake<S>
where
S: Service<(), Response = IO, Error = BoxError> + Clone + Send + 'static,
S::Future: Send + 'static,
IO: hyper::rt::Read + hyper::rt::Write + Unpin + Send + 'static,
{
type Response = H2Connection;
type Error = BoxError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, (): ()) -> Self::Future {
let io_fut = self.inner.call(());
let metrics = self.metrics.clone();
let attrs = self.attrs.clone();
let keep_alive_interval = self.keep_alive_interval;
let keep_alive_timeout = self.keep_alive_timeout;
let keep_alive_while_idle = self.keep_alive_while_idle;
Box::pin(async move {
let io = io_fut.await?;
let (sender, conn) = http2::Builder::new(TokioExecutor::new())
.timer(TokioTimer::new())
.keep_alive_interval(keep_alive_interval)
.keep_alive_timeout(keep_alive_timeout)
.keep_alive_while_idle(keep_alive_while_idle)
.handshake(io)
.await
.map_err(|source| BoxError::from(HttpClientError::Handshake { source }))?;
tokio::spawn(async move {
if let Err(e) = conn.await {
log::debug!(error:% = e; "HTTP/2 connection error");
}
});
Ok(H2Connection::new(sender, &metrics, attrs))
})
}
}