use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use hyper_util::rt::{TokioIo, TokioTimer};
use hyper_util::server::conn::auto::Builder;
use hyper_util::server::graceful::GracefulConnection;
use hyper_util::service::TowerToHyperService;
use pin_project_lite::pin_project;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::TcpListener;
use tokio::sync::{watch, OwnedSemaphorePermit, Semaphore};
use tokio::task::JoinSet;
use tonic::transport::server::Connected;
use crate::service::{ConnectionInfo, ProxyService};
use crate::upstream::Upstream;
mod idle;
#[derive(Clone, Debug)]
pub struct ServeOptions {
max_connections: Option<usize>,
tls: Option<Arc<rustls::ServerConfig>>,
idle_timeout: Option<Duration>,
header_read_timeout: Duration,
tls_handshake_timeout: Duration,
drain_timeout: Option<Duration>,
}
impl Default for ServeOptions {
fn default() -> Self {
Self {
max_connections: None,
tls: None,
idle_timeout: Some(Duration::from_secs(60)),
header_read_timeout: Duration::from_secs(30),
tls_handshake_timeout: Duration::from_secs(10),
drain_timeout: Some(Duration::from_secs(25)),
}
}
}
impl ServeOptions {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn drain_timeout(mut self, timeout: Option<Duration>) -> Self {
self.drain_timeout = timeout;
self
}
#[must_use]
pub fn idle_timeout(mut self, timeout: Option<Duration>) -> Self {
self.idle_timeout = timeout;
self
}
#[must_use]
pub fn header_read_timeout(mut self, timeout: Duration) -> Self {
self.header_read_timeout = timeout;
self
}
#[must_use]
pub fn tls_handshake_timeout(mut self, timeout: Duration) -> Self {
self.tls_handshake_timeout = timeout;
self
}
#[must_use]
pub fn max_connections(mut self, max: usize) -> Self {
assert!(max > 0, "max_connections must be at least 1");
self.max_connections = Some(max.min(Semaphore::MAX_PERMITS));
self
}
#[must_use]
pub fn tls(mut self, mut config: rustls::ServerConfig) -> Self {
if config.alpn_protocols.is_empty() {
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
}
self.tls = Some(Arc::new(config));
self
}
}
pub async fn serve<U: Upstream>(
listener: TcpListener,
service: ProxyService<U>,
) -> std::io::Result<()> {
serve_with(listener, service, ServeOptions::new()).await
}
pub async fn serve_with<U: Upstream>(
listener: TcpListener,
service: ProxyService<U>,
options: ServeOptions,
) -> std::io::Result<()> {
serve_with_shutdown(listener, service, options, std::future::pending()).await
}
pub async fn serve_with_shutdown<U, F>(
listener: TcpListener,
service: ProxyService<U>,
options: ServeOptions,
signal: F,
) -> std::io::Result<()>
where
U: Upstream,
F: Future<Output = ()>,
{
let slots = options
.max_connections
.map(|max| Arc::new(Semaphore::new(max)));
let acceptor = options.tls.map(tokio_rustls::TlsAcceptor::from);
let handshake_timeout = options.tls_handshake_timeout;
let limits = ConnectionLimits {
idle_timeout: options.idle_timeout,
header_read_timeout: options.header_read_timeout,
};
let (stop, stopping) = watch::channel(false);
let mut connections = JoinSet::new();
let streams = StreamExecutor::default();
let _streams_end_with_this_future = streams.cancel_on_drop();
tokio::pin!(signal);
'serve: loop {
let slot = loop {
tokio::select! {
biased;
() = &mut signal => break 'serve,
Some(_) = connections.join_next() => {}
slot = take_slot(slots.as_ref()) => break slot,
}
};
let tcp = loop {
tokio::select! {
biased;
() = &mut signal => break 'serve,
Some(_) = connections.join_next() => {}
accepted = listener.accept() => match accepted {
Ok((tcp, _)) => break tcp,
Err(error) => tokio::select! {
biased;
() = &mut signal => break 'serve,
() = accept_failed(error) => {}
},
},
}
};
let service = service.clone();
let acceptor = acceptor.clone();
let stopping = stopping.clone();
let streams = streams.clone();
connections.spawn(async move {
if let Err(error) = tcp.set_nodelay(true) {
tracing::debug!(%error, "cannot set TCP_NODELAY");
}
match acceptor {
None => {
let service = service.for_connection(tcp.connect_info());
let io = SlotIo { io: tcp, slot };
serve_connection(io, service, limits, streams, stopping).await;
}
Some(acceptor) => {
let handshake = tokio::time::timeout(handshake_timeout, acceptor.accept(tcp));
let stream = tokio::select! {
handshake = handshake => match handshake {
Ok(Ok(stream)) => stream,
Ok(Err(error)) => {
tracing::debug!(%error, "TLS handshake failed");
return;
}
Err(_) => {
tracing::debug!("TLS handshake timed out");
return;
}
},
() = stopped(stopping.clone()) => return,
};
let service =
service.for_connection(ConnectionInfo::tls(stream.connect_info()));
let io = SlotIo { io: stream, slot };
serve_connection(io, service, limits, streams, stopping).await;
}
}
});
}
drop(listener);
stop.send_replace(true);
streams.close();
let drained = async {
while connections.join_next().await.is_some() {}
streams.finished().await;
};
match options.drain_timeout {
None => drained.await,
Some(timeout) => {
if tokio::time::timeout(timeout, drained).await.is_err() {
tracing::warn!(
open = connections.len(),
"shutdown drain timed out; closing the connections still open"
);
connections.shutdown().await;
streams.stop().await;
}
}
}
Ok(())
}
#[derive(Clone, Default)]
struct StreamExecutor {
tasks: tokio_util::task::TaskTracker,
cancel: tokio_util::sync::CancellationToken,
}
impl StreamExecutor {
fn close(&self) {
self.tasks.close();
}
async fn finished(&self) {
self.tasks.wait().await;
}
async fn stop(&self) {
self.cancel.cancel();
self.tasks.wait().await;
}
fn cancel_on_drop(&self) -> tokio_util::sync::DropGuard {
self.cancel.clone().drop_guard()
}
}
impl<F> hyper::rt::Executor<F> for StreamExecutor
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
fn execute(&self, future: F) {
self.tasks
.spawn(self.cancel.clone().run_until_cancelled_owned(future));
}
}
async fn take_slot(slots: Option<&Arc<Semaphore>>) -> Option<OwnedSemaphorePermit> {
match slots {
Some(slots) => Some(
Arc::clone(slots)
.acquire_owned()
.await
.expect("the connection semaphore is never closed"),
),
None => None,
}
}
async fn stopped(mut stopping: watch::Receiver<bool>) {
stopping.wait_for(|stop| *stop).await.ok();
}
pin_project! {
struct SlotIo<I> {
#[pin]
io: I,
slot: Option<OwnedSemaphorePermit>,
}
}
impl<I: AsyncRead> AsyncRead for SlotIo<I> {
#[inline]
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
self.project().io.poll_read(cx, buf)
}
}
impl<I: AsyncWrite> AsyncWrite for SlotIo<I> {
#[inline]
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<std::io::Result<usize>> {
self.project().io.poll_write(cx, buf)
}
#[inline]
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
self.project().io.poll_flush(cx)
}
#[inline]
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
self.project().io.poll_shutdown(cx)
}
#[inline]
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> Poll<std::io::Result<usize>> {
self.project().io.poll_write_vectored(cx, bufs)
}
#[inline]
fn is_write_vectored(&self) -> bool {
self.io.is_write_vectored()
}
}
#[derive(Clone, Copy, Debug)]
struct ConnectionLimits {
idle_timeout: Option<Duration>,
header_read_timeout: Duration,
}
async fn serve_connection<U, I>(
io: I,
service: ProxyService<U>,
limits: ConnectionLimits,
streams: StreamExecutor,
stopping: watch::Receiver<bool>,
) where
U: Upstream,
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let mut builder = Builder::new(streams);
builder
.http1()
.timer(TokioTimer::new())
.header_read_timeout(limits.header_read_timeout);
builder.http2().timer(TokioTimer::new());
let served = match limits.idle_timeout {
None => {
let connection = builder.serve_connection_with_upgrades(
TokioIo::new(io),
TowerToHyperService::new(service),
);
drive(connection, std::future::pending(), stopping).await
}
Some(timeout) => {
let activity = Arc::new(idle::Activity::default());
let service = idle::Tracked {
inner: service,
activity: Arc::clone(&activity),
};
let connection = builder.serve_connection_with_upgrades(
TokioIo::new(io),
TowerToHyperService::new(service),
);
drive(connection, activity.idle_for(timeout), stopping).await
}
};
if let Err(error) = served {
tracing::debug!(%error, "connection ended");
}
}
async fn drive<C: GracefulConnection>(
connection: C,
idle: impl Future<Output = ()>,
stopping: watch::Receiver<bool>,
) -> Result<(), C::Error> {
tokio::pin!(connection);
tokio::select! {
served = connection.as_mut() => served,
() = idle => {
connection.as_mut().graceful_shutdown();
connection.await
}
() = stopped(stopping) => {
connection.as_mut().graceful_shutdown();
connection.await
}
}
}
async fn accept_failed(error: std::io::Error) {
use std::io::ErrorKind;
if matches!(
error.kind(),
ErrorKind::ConnectionRefused | ErrorKind::ConnectionAborted | ErrorKind::ConnectionReset
) {
return;
}
tracing::error!(%error, "accepting a connection failed; retrying in 1s");
tokio::time::sleep(Duration::from_secs(1)).await;
}
#[cfg(test)]
mod tests;