#![cfg_attr(docsrs, feature(doc_cfg))]
use std::io::ErrorKind;
use std::io::Write;
use std::io::{self};
use std::net::SocketAddr;
use std::str::FromStr;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum H3Congestion {
#[default]
Cubic,
NewReno,
Bbr,
}
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub drain_timeout: Duration,
pub header_read_timeout: Option<Duration>,
pub keep_alive: bool,
pub keep_alive_timeout: Option<Duration>,
pub h2_max_concurrent_streams: u32,
pub h2_max_header_list_size: u32,
pub h2_max_send_buf_size: usize,
pub h2_max_pending_accept_reset_streams: usize,
pub h2_keep_alive_interval: Option<Duration>,
pub h3_max_concurrent_bidi_streams: u32,
pub h3_max_concurrent_uni_streams: u32,
pub h3_max_idle_timeout: Option<Duration>,
pub h3_congestion: H3Congestion,
pub h3_enable_datagrams: bool,
pub h3_use_retry: bool,
pub h3_goaway_grace: Duration,
pub max_connections: Option<usize>,
pub proxy_read_timeout: Duration,
pub tls_handshake_timeout: Duration,
pub accept_backoff: AcceptBackoff,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
drain_timeout: Duration::from_secs(30),
header_read_timeout: Some(Duration::from_secs(30)),
keep_alive: true,
keep_alive_timeout: None,
h2_max_concurrent_streams: 100,
h2_max_header_list_size: 16 * 1024,
h2_max_send_buf_size: 1024 * 1024,
h2_max_pending_accept_reset_streams: 50,
h2_keep_alive_interval: None,
h3_max_concurrent_bidi_streams: 100,
h3_max_concurrent_uni_streams: 8,
h3_max_idle_timeout: Some(Duration::from_secs(30)),
h3_congestion: H3Congestion::default(),
h3_enable_datagrams: false,
h3_use_retry: false,
h3_goaway_grace: Duration::from_secs(10),
max_connections: None,
proxy_read_timeout: Duration::from_secs(10),
tls_handshake_timeout: Duration::from_secs(10),
accept_backoff: AcceptBackoff::new(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AcceptBackoff {
current: Duration,
max: Duration,
}
impl Default for AcceptBackoff {
fn default() -> Self {
Self::new()
}
}
impl AcceptBackoff {
#[must_use]
pub const fn new() -> Self {
Self {
current: Duration::from_millis(5),
max: Duration::from_secs(1),
}
}
#[inline]
pub fn reset(&mut self) {
self.current = Duration::from_millis(5);
}
pub async fn sleep_and_grow(&mut self) {
let d = self.current_and_grow();
tokio::time::sleep(d).await;
}
pub fn current_and_grow(&mut self) -> Duration {
let d = self.current;
self.current = (self.current * 2).min(self.max);
d
}
}
#[cfg(not(feature = "compio"))]
mod server;
mod builder;
#[cfg(feature = "tls")]
pub use builder::ClientAuth;
#[cfg(feature = "compio")]
pub use builder::CompioServer;
#[cfg(feature = "compio")]
pub use builder::CompioServerBuilder;
#[cfg(feature = "tls")]
pub use builder::ReloadableResolver;
#[cfg(not(feature = "compio"))]
pub use builder::Server;
#[cfg(not(feature = "compio"))]
pub use builder::ServerBuilder;
pub use builder::ServerHandle;
pub use builder::TlsCert;
#[cfg(feature = "tls")]
pub use builder::build_rustls_server_config;
pub use builder::either;
#[cfg(not(feature = "compio"))]
pub use server::serve;
#[cfg(not(feature = "compio"))]
pub use server::serve_with_config;
#[cfg(not(feature = "compio"))]
pub use server::serve_with_shutdown;
#[cfg(not(feature = "compio"))]
pub use server::serve_with_shutdown_and_config;
#[cfg(feature = "compio")]
#[cfg_attr(docsrs, doc(cfg(feature = "compio")))]
pub mod server_compio;
#[cfg(feature = "compio")]
pub use server_compio::serve;
#[cfg(feature = "compio")]
pub use server_compio::serve_with_config;
#[cfg(feature = "compio")]
pub use server_compio::serve_with_shutdown;
#[cfg(feature = "compio")]
pub use server_compio::serve_with_shutdown_and_config;
#[cfg(all(not(feature = "compio-tls"), feature = "tls"))]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub mod server_tls;
#[cfg(all(not(feature = "compio"), feature = "tls"))]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub use server_tls::serve_tls;
#[cfg(all(not(feature = "compio"), feature = "tls"))]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub use server_tls::serve_tls_with_config;
#[cfg(all(not(feature = "compio"), feature = "tls"))]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub use server_tls::serve_tls_with_shutdown;
#[cfg(all(not(feature = "compio"), feature = "tls"))]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub use server_tls::serve_tls_with_shutdown_and_config;
#[cfg(feature = "compio-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "compio-tls")))]
pub mod server_tls_compio;
#[cfg(feature = "compio-tls")]
pub use server_tls_compio::serve_tls;
#[cfg(feature = "compio-tls")]
pub use server_tls_compio::serve_tls_with_config;
#[cfg(feature = "compio-tls")]
pub use server_tls_compio::serve_tls_with_shutdown;
#[cfg(feature = "compio-tls")]
pub use server_tls_compio::serve_tls_with_shutdown_and_config;
#[cfg(all(feature = "http3", not(feature = "compio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "http3")))]
pub mod server_h3;
#[cfg(all(feature = "http3", not(feature = "compio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "http3")))]
pub use server_h3::serve_h3;
#[cfg(all(feature = "http3", not(feature = "compio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "http3")))]
pub use server_h3::serve_h3_with_config;
#[cfg(all(feature = "http3", not(feature = "compio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "http3")))]
pub use server_h3::serve_h3_with_shutdown;
#[cfg(all(feature = "http3", not(feature = "compio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "http3")))]
pub use server_h3::serve_h3_with_shutdown_and_config;
pub mod server_tcp;
#[cfg(all(feature = "http2", not(feature = "compio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
pub mod server_h2c;
#[cfg(all(feature = "http2", not(feature = "compio")))]
pub use server_h2c::serve_h2c;
#[cfg(all(feature = "http2", not(feature = "compio")))]
pub use server_h2c::serve_h2c_with_config;
#[cfg(all(feature = "http2", not(feature = "compio")))]
pub use server_h2c::serve_h2c_with_shutdown;
#[cfg(all(feature = "http2", not(feature = "compio")))]
pub use server_h2c::serve_h2c_with_shutdown_and_config;
pub mod server_udp;
#[cfg(all(unix, not(feature = "compio")))]
pub mod server_unix;
#[cfg(not(feature = "compio"))]
pub mod proxy_protocol;
#[cfg(feature = "socket-activation")]
#[cfg_attr(docsrs, doc(cfg(feature = "socket-activation")))]
pub mod socket_activation;
#[cfg(all(target_os = "linux", feature = "vsock", not(feature = "compio")))]
#[cfg_attr(docsrs, doc(cfg(feature = "vsock")))]
pub mod server_vsock;
#[cfg(not(feature = "compio"))]
pub async fn bind_with_port_fallback(addr: &str) -> io::Result<tokio::net::TcpListener> {
let mut socket_addr =
SocketAddr::from_str(addr).map_err(|e| io::Error::new(ErrorKind::InvalidInput, e))?;
let start_port = socket_addr.port();
loop {
let addr_str = socket_addr.to_string();
match tokio::net::TcpListener::bind(&addr_str).await {
Ok(listener) => {
if socket_addr.port() != start_port {
println!(
"Port {} was in use, starting on {} instead",
start_port,
socket_addr.port()
);
}
return Ok(listener);
}
Err(err) if err.kind() == ErrorKind::AddrInUse => {
let curr_port = socket_addr.port();
if curr_port == u16::MAX {
return Err(err);
}
let next_port = curr_port + 1;
let proceed =
tokio::task::spawn_blocking(move || ask_to_use_next_port(curr_port, next_port))
.await
.map_err(io::Error::other)??;
if !proceed {
return Err(err);
}
socket_addr.set_port(next_port);
}
Err(err) => return Err(err),
}
}
}
#[cfg(feature = "compio")]
pub async fn bind_with_port_fallback(addr: &str) -> io::Result<compio::net::TcpListener> {
let mut socket_addr =
SocketAddr::from_str(addr).map_err(|e| io::Error::new(ErrorKind::InvalidInput, e))?;
let start_port = socket_addr.port();
loop {
let addr_str = socket_addr.to_string();
match compio::net::TcpListener::bind(&addr_str).await {
Ok(listener) => {
if socket_addr.port() != start_port {
println!(
"Port {} was in use, starting on {} instead",
start_port,
socket_addr.port()
);
}
return Ok(listener);
}
Err(err) if err.kind() == ErrorKind::AddrInUse => {
let curr_port = socket_addr.port();
if curr_port == u16::MAX {
return Err(err);
}
let next_port = curr_port + 1;
let proceed =
compio::runtime::spawn_blocking(move || ask_to_use_next_port(curr_port, next_port))
.await
.map_err(|_| io::Error::other("compio spawn_blocking panicked"))??;
if !proceed {
return Err(err);
}
socket_addr.set_port(next_port);
}
Err(err) => return Err(err),
}
}
}
fn ask_to_use_next_port(current: u16, next: u16) -> io::Result<bool> {
loop {
print!("Port {current} is already in use. Start on {next} instead? [Y/n]: ");
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let trimmed = input.trim();
if trimmed.is_empty()
|| trimmed.eq_ignore_ascii_case("y")
|| trimmed.eq_ignore_ascii_case("yes")
{
return Ok(true);
}
if trimmed.eq_ignore_ascii_case("n") || trimmed.eq_ignore_ascii_case("no") {
return Ok(false);
}
println!("Please answer 'y' or 'n'.");
}
}