use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use hyper_util::rt::{TokioExecutor, TokioIo, TokioTimer};
use hyper_util::server::conn::auto::Builder;
use hyper_util::service::TowerToHyperService;
use pin_project_lite::pin_project;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::TcpListener;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
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,
}
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),
}
}
}
impl ServeOptions {
pub fn new() -> Self {
Self::default()
}
#[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<()> {
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,
};
loop {
let slot = match &slots {
Some(slots) => Some(
slots
.clone()
.acquire_owned()
.await
.expect("the connection semaphore is never closed"),
),
None => None,
};
let tcp = match listener.accept().await {
Ok((tcp, _)) => tcp,
Err(error) => {
accept_failed(error).await;
continue;
}
};
let service = service.clone();
let acceptor = acceptor.clone();
tokio::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());
serve_connection(SlotIo { io: tcp, slot }, service, limits).await;
}
Some(acceptor) => {
let stream =
match tokio::time::timeout(handshake_timeout, acceptor.accept(tcp)).await {
Ok(Ok(stream)) => stream,
Ok(Err(error)) => {
tracing::debug!(%error, "TLS handshake failed");
return;
}
Err(_) => {
tracing::debug!("TLS handshake timed out");
return;
}
};
let service =
service.for_connection(ConnectionInfo::tls(stream.connect_info()));
serve_connection(SlotIo { io: stream, slot }, service, limits).await;
}
}
});
}
}
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)
where
U: Upstream,
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let mut builder = Builder::new(TokioExecutor::new());
builder
.http1()
.timer(TokioTimer::new())
.header_read_timeout(limits.header_read_timeout);
builder.http2().timer(TokioTimer::new());
let served = match limits.idle_timeout {
None => {
builder
.serve_connection_with_upgrades(TokioIo::new(io), TowerToHyperService::new(service))
.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),
);
tokio::pin!(connection);
tokio::select! {
served = connection.as_mut() => served,
() = activity.idle_for(timeout) => {
connection.as_mut().graceful_shutdown();
connection.await
}
}
}
};
if let Err(error) = served {
tracing::debug!(%error, "connection ended");
}
}
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;