use std::future::Future;
use std::pin::Pin;
use std::pin::pin;
use std::time::Duration;
use http::Request;
use http::Response;
use hyper_util::rt::TokioExecutor;
use hyper_util::server::conn::auto;
use tracing::debug;
use tracing::trace;
use crate::ActiveConnections;
use crate::BoxError;
use crate::ConnectionId;
use crate::fuse::Fuse;
pub async fn serve_connection<IO, S, B, C>(
hyper_io: IO,
hyper_svc: S,
builder: auto::Builder<TokioExecutor>,
graceful_shutdown_token: tokio_util::sync::CancellationToken,
max_connection_age: Option<Duration>,
max_connection_age_grace: Option<Duration>,
on_connection_close: C,
) where
B: http_body::Body + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
IO: hyper::rt::Read + hyper::rt::Write + Send + Unpin + 'static,
S: hyper::service::Service<Request<hyper::body::Incoming>, Response = Response<B>> + 'static,
S::Future: Send + 'static,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
if builder.is_http1_available() {
let conn = pin!(builder.serve_connection_with_upgrades(hyper_io, hyper_svc));
drive_connection(
conn,
graceful_shutdown_token,
max_connection_age,
max_connection_age_grace,
)
.await;
} else {
let conn = pin!(builder.serve_connection(hyper_io, hyper_svc));
drive_connection(
conn,
graceful_shutdown_token,
max_connection_age,
max_connection_age_grace,
)
.await;
}
trace!("connection closed");
drop(on_connection_close);
}
trait GracefulConnection: Future<Output = Result<(), BoxError>> {
fn graceful_shutdown(self: Pin<&mut Self>);
}
impl<IO, S, B> GracefulConnection for auto::Connection<'_, IO, S, TokioExecutor>
where
B: http_body::Body + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
IO: hyper::rt::Read + hyper::rt::Write + Unpin + 'static,
S: hyper::service::Service<Request<hyper::body::Incoming>, Response = Response<B>>,
S::Future: Send + 'static,
S::Error: Into<BoxError>,
{
fn graceful_shutdown(self: Pin<&mut Self>) {
auto::Connection::graceful_shutdown(self)
}
}
impl<IO, S, B> GracefulConnection for auto::UpgradeableConnection<'_, IO, S, TokioExecutor>
where
B: http_body::Body + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
IO: hyper::rt::Read + hyper::rt::Write + Send + Unpin + 'static,
S: hyper::service::Service<Request<hyper::body::Incoming>, Response = Response<B>>,
S::Future: Send + 'static,
S::Error: Into<BoxError>,
{
fn graceful_shutdown(self: Pin<&mut Self>) {
auto::UpgradeableConnection::graceful_shutdown(self)
}
}
async fn drive_connection<C>(
mut conn: Pin<&mut C>,
graceful_shutdown_token: tokio_util::sync::CancellationToken,
max_connection_age: Option<Duration>,
max_connection_age_grace: Option<Duration>,
) where
C: GracefulConnection,
{
let mut sig = pin!(Fuse::new(graceful_shutdown_token.cancelled_owned()));
let sleep = sleep_or_pending(max_connection_age);
tokio::pin!(sleep);
let mut in_grace_period = false;
loop {
tokio::select! {
_ = &mut sig => {
conn.as_mut().graceful_shutdown();
if !in_grace_period {
in_grace_period = true;
sleep.set(sleep_or_pending(max_connection_age_grace));
}
}
rv = &mut conn => {
if let Err(err) = rv {
debug!("failed serving connection: {:#}", err);
}
break;
},
_ = &mut sleep => {
if in_grace_period {
debug!("max connection age grace period expired, closing connection");
break;
}
conn.as_mut().graceful_shutdown();
in_grace_period = true;
sleep.set(sleep_or_pending(max_connection_age_grace));
},
}
}
}
async fn sleep_or_pending(wait_for: Option<Duration>) {
match wait_for {
Some(wait) => tokio::time::sleep(wait).await,
None => std::future::pending().await,
};
}
pub(crate) struct OnConnectionClose<A> {
id: ConnectionId,
active_connections: ActiveConnections<A>,
}
impl<A> OnConnectionClose<A> {
pub(crate) fn new(id: ConnectionId, active_connections: ActiveConnections<A>) -> Self {
Self {
id,
active_connections,
}
}
}
impl<A> Drop for OnConnectionClose<A> {
fn drop(&mut self) {
self.active_connections.write().unwrap().remove(&self.id);
}
}