#[cfg(any(feature = "tor", feature = "i2p"))]
pub(crate) mod conn;
#[cfg(feature = "http3")]
mod h3;
mod http;
#[cfg(feature = "i2p")]
pub mod i2p;
mod multi;
#[cfg(feature = "tor")]
pub mod tor;
pub use multi::MultiServer;
use crate::routing::CompiledRouter;
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use tokio::net::TcpListener;
#[cfg(feature = "tls")]
use crate::http::response::Body;
#[cfg(feature = "tls")]
use hyper::service::service_fn;
#[cfg(feature = "tls")]
use hyper::{Request, Response};
#[cfg(any(feature = "cert-gen", feature = "lets-encrypt", feature = "http3"))]
use tokio_rustls::TlsAcceptor;
pub(crate) const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
#[cfg(feature = "tls")]
pub(crate) const TLS_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(3);
#[cfg(feature = "lets-encrypt")]
const FIRST_CERT_TIMEOUT: Duration = Duration::from_secs(30);
thread_local! {
pub(crate) static IS_LOCAL_WORKER: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub(crate) fn jittered_delay(min: Duration, max: Duration) -> Duration {
use std::hash::{BuildHasher, Hasher};
if max <= min {
return min;
}
let mut hasher = std::collections::hash_map::RandomState::new().build_hasher();
let now_nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_nanos());
hasher.write_u128(now_nanos);
let span_nanos = max.checked_sub(min).unwrap_or(max).as_nanos().max(1);
let offset_nanos = (u128::from(hasher.finish()) % span_nanos).min(u128::from(u64::MAX));
min + Duration::from_nanos(u64::try_from(offset_nanos).unwrap_or(u64::MAX))
}
fn bind_reuseport(addr: std::net::SocketAddr) -> Result<std::net::TcpListener, std::io::Error> {
use socket2::{Domain, Protocol, Socket, Type};
let domain = Domain::for_address(addr);
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
socket.set_reuse_address(true)?;
#[cfg(unix)]
{
socket.set_reuse_port(true)?;
}
socket.bind(&addr.into())?;
socket.set_nonblocking(true)?;
socket.listen(4096)?;
Ok(std::net::TcpListener::from(socket))
}
async fn run_worker_pool<S, F, Fut>(
server: Server<S>,
addr: std::net::SocketAddr,
redirect_info: Option<(std::net::SocketAddr, u16)>,
serve_fn: F,
) -> Result<(), std::io::Error>
where
S: Clone + Send + Sync + 'static,
F: Fn(Server<S>, TcpListener) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), std::io::Error>> + Send + 'static,
{
let cores = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
let core_ids = core_affinity::get_core_ids().unwrap_or_default();
let mut handles = Vec::new();
let server = Arc::new(server);
let serve_fn = Arc::new(serve_fn);
let (bind_tx, bind_rx) = std::sync::mpsc::channel::<Result<(), std::io::Error>>();
for i in 0..cores {
let server = server.clone();
let serve_fn = serve_fn.clone();
let core_id = core_ids.get(i).copied();
let bind_tx = bind_tx.clone();
let handle = std::thread::Builder::new()
.name(format!("tachyon-worker-{i}"))
.stack_size(512 * 1024)
.spawn(move || {
if let Some(id) = core_id {
let _ = core_affinity::set_for_current(id);
}
let Ok(rt) = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
else {
tracing::error!("Failed to build Tokio runtime for worker thread");
return;
};
let local = tokio::task::LocalSet::new();
local.block_on(&rt, async move {
IS_LOCAL_WORKER.with(|flag| flag.set(true));
#[cfg(not(feature = "tls"))]
let _ = &redirect_info;
#[cfg(feature = "tls")]
if let Some((r_addr, https_port)) = redirect_info {
let r_listener_res = bind_reuseport(r_addr).and_then(TcpListener::from_std);
match r_listener_res {
Ok(l) => {
tokio::task::spawn_local(async move {
serve_http_redirect_and_challenges(l, https_port).await;
});
}
Err(e) => {
tracing::error!("Worker redirect bind error: {e}");
}
}
}
let listener_res = bind_reuseport(addr).and_then(TcpListener::from_std);
let listener = match listener_res {
Ok(l) => {
let _ = bind_tx.send(Ok(()));
l
}
Err(e) => {
tracing::error!("Worker bind error: {e}");
let _ = bind_tx.send(Err(e));
return;
}
};
let server_clone = (*server).clone();
let _ = serve_fn(server_clone, listener).await;
});
})?;
handles.push(handle);
}
drop(bind_tx);
let bind_results = tokio::task::spawn_blocking(move || {
(0..cores).filter_map(|_| bind_rx.recv().ok()).collect::<Vec<_>>()
})
.await
.unwrap_or_default();
let bound = bind_results.iter().filter(|r| r.is_ok()).count();
if bound == 0 {
return Err(bind_results
.into_iter()
.find_map(std::result::Result::err)
.unwrap_or_else(|| {
std::io::Error::other("all worker threads failed to bind their listener")
}));
}
if bound < cores {
tracing::warn!(
"Only {bound}/{cores} worker threads bound successfully; running in a degraded state"
);
}
let _ = handles;
std::future::pending::<()>().await;
Ok(())
}
#[derive(Debug)]
pub struct Server<S> {
pub(crate) router: CompiledRouter<S>,
pub max_body_size: usize,
pub max_connections: usize,
#[cfg(feature = "tls")]
pub(crate) tls_policy: Option<crate::tls::TlsPolicy>,
pub(crate) response_jitter: Option<(Duration, Duration)>,
}
impl<S> Clone for Server<S>
where
S: Clone,
{
fn clone(&self) -> Self {
Self {
router: self.router.clone(),
max_body_size: self.max_body_size,
max_connections: self.max_connections,
#[cfg(feature = "tls")]
tls_policy: self.tls_policy.clone(),
response_jitter: self.response_jitter,
}
}
}
impl Server<()> {
#[must_use]
#[allow(clippy::expect_used)]
pub fn new(router: crate::routing::Router<()>) -> Self {
let compiled = router.compile().expect("Router compilation failed");
Self {
router: compiled,
max_body_size: 2 * 1024 * 1024, max_connections: 25_600,
#[cfg(feature = "tls")]
tls_policy: None,
response_jitter: None,
}
}
}
impl<S> Server<S>
where
S: Clone + Send + Sync + 'static,
{
#[must_use]
pub const fn max_body_size(mut self, size: usize) -> Self {
self.max_body_size = size;
self
}
#[must_use]
pub const fn max_connections(mut self, limit: usize) -> Self {
self.max_connections = limit;
self
}
#[must_use]
pub const fn response_jitter(mut self, min: Duration, max: Duration) -> Self {
self.response_jitter = Some((min, max));
self
}
#[cfg(feature = "tls")]
#[must_use]
pub fn crypto_provider(self, provider: Arc<rustls::crypto::CryptoProvider>) -> Self {
self.tls_policy(crate::tls::TlsPolicy::with_provider(provider))
}
#[cfg(feature = "tls")]
#[must_use]
pub fn tls_policy(mut self, policy: crate::tls::TlsPolicy) -> Self {
self.tls_policy = Some(policy);
self
}
#[cfg(any(
feature = "cert-gen",
feature = "lets-encrypt",
all(feature = "tor", feature = "tls"),
))]
pub(crate) fn effective_tls_policy(&self) -> crate::tls::TlsPolicy {
self.tls_policy.clone().unwrap_or_default()
}
pub fn with_http(self, listener: TcpListener) -> MultiServer<S> {
MultiServer::new(self).with_http(listener)
}
#[cfg(feature = "tls")]
pub fn with_https(self, listener: TcpListener, config: rustls::ServerConfig) -> MultiServer<S> {
MultiServer::new(self).with_https(listener, config)
}
#[cfg(feature = "http3")]
pub fn with_h3(self, quic_server: s2n_quic::Server) -> MultiServer<S> {
MultiServer::new(self).with_h3(quic_server)
}
#[cfg(feature = "tor")]
pub fn with_onion(self, config: tor::OnionConfig) -> MultiServer<S> {
MultiServer::new(self).with_onion(config)
}
#[cfg(feature = "i2p")]
pub fn with_i2p(self, config: i2p::I2pConfig) -> MultiServer<S> {
MultiServer::new(self).with_i2p(config)
}
pub async fn start_http_addr(self, addr: std::net::SocketAddr) -> Result<(), std::io::Error> {
run_worker_pool(self, addr, None, |server, listener| async move {
server.serve_http(listener).await
})
.await
}
pub async fn start_http(self, http_addr: &str) -> Result<(), std::io::Error> {
let addr: std::net::SocketAddr = http_addr
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
self.start_http_addr(addr).await
}
#[cfg(feature = "tls")]
pub async fn start_https_with_config_addr(
self,
addr: std::net::SocketAddr,
config: rustls::ServerConfig,
) -> Result<(), std::io::Error> {
let config = Arc::new(config);
run_worker_pool(self, addr, None, move |server, listener| {
let config = config.clone();
async move { server.serve_https_config(listener, (*config).clone()).await }
})
.await
}
#[cfg(feature = "tls")]
pub async fn start_https_with_config(
self,
tls_addr: &str,
config: rustls::ServerConfig,
) -> Result<(), std::io::Error> {
let addr: std::net::SocketAddr = tls_addr
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
self.start_https_with_config_addr(addr, config).await
}
#[cfg(all(feature = "tls", feature = "http3"))]
pub async fn start_https_and_h3_with_config(
self,
tls_addr: &str,
mut config: rustls::ServerConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
enforce_fips_compliance()?;
config.alpn_protocols = alpn_protocols(true);
let config = Arc::new(config);
let quic_tls = s2n_quic::provider::tls::rustls::Server::from(config.clone());
let quic_limits = s2n_quic::provider::limits::Limits::new()
.with_data_window(1_048_576)?
.with_bidirectional_local_data_window(1_048_576)?
.with_bidirectional_remote_data_window(1_048_576)?
.with_initial_round_trip_time(Duration::from_millis(100))?
.with_max_open_remote_bidirectional_streams(4096)?
.with_ack_elicitation_interval(4)?
.with_active_connection_migration(false)?
.with_max_active_connection_ids(2)?
.with_max_handshake_duration(Duration::from_secs(5))?;
let quic_server = s2n_quic::Server::builder()
.with_tls(quic_tls)?
.with_limits(quic_limits)?
.with_io(tls_addr)?
.start()?;
let server_h3 = self.clone();
drop(tokio::spawn(async move {
let _ = server_h3.serve_h3(quic_server).await;
}));
let addr: std::net::SocketAddr = tls_addr
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
let tls_acceptor = TlsAcceptor::from(config);
let tls_acceptor = Arc::new(tls_acceptor);
run_worker_pool(self, addr, None, move |server, listener| {
let tls_acceptor = tls_acceptor.clone();
async move { server.serve_https(listener, (*tls_acceptor).clone()).await }
})
.await?;
Ok(())
}
#[cfg(feature = "cert-gen")]
pub async fn start_all(
self,
tls_addr: &str,
cleartext_addr: Option<&str>,
cert_pem: String,
key_pem: String,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.start_all_inner(tls_addr, cleartext_addr, cert_pem, key_pem)
.await
}
#[cfg(feature = "lets-encrypt")]
pub async fn serve_all_acme(
self,
tls_addr: &str,
cleartext_addr: &str,
domains: Vec<String>,
email: String,
cache_dir: impl Into<std::path::PathBuf>,
staging: bool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use crate::tls::acme::AcmeManager;
enforce_fips_compliance()?;
let acme = AcmeManager::new(cache_dir, domains, email, staging);
let resolver = acme.resolver();
acme.start();
let wait_start = tokio::time::Instant::now();
while !resolver.has_certificate() {
if wait_start.elapsed() >= FIRST_CERT_TIMEOUT {
tracing::warn!(
"[acme] No certificate ready after {:?}; starting TLS listener anyway — \
connections will fail until provisioning completes",
FIRST_CERT_TIMEOUT
);
break;
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
let policy = self.effective_tls_policy();
let mut tls_config = rustls::ServerConfig::builder_with_provider(policy.provider())
.with_protocol_versions(policy.versions())
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("TLS version configuration failed: {e}"),
)
})?
.with_no_client_auth()
.with_cert_resolver(resolver);
#[cfg(feature = "http3")]
{
tls_config.alpn_protocols = alpn_protocols(true);
}
#[cfg(not(feature = "http3"))]
{
tls_config.alpn_protocols = alpn_protocols(false);
}
let tls_config = Arc::new(tls_config);
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
#[cfg(feature = "http3")]
{
let quic_tls = s2n_quic::provider::tls::rustls::Server::from(tls_config);
let quic_limits = s2n_quic::provider::limits::Limits::new()
.with_data_window(1_048_576)?
.with_bidirectional_local_data_window(1_048_576)?
.with_bidirectional_remote_data_window(1_048_576)?
.with_initial_round_trip_time(Duration::from_millis(100))?
.with_max_open_remote_bidirectional_streams(4096)?
.with_ack_elicitation_interval(4)?
.with_active_connection_migration(false)?
.with_max_active_connection_ids(2)?
.with_max_handshake_duration(Duration::from_secs(5))?;
let quic_server = s2n_quic::Server::builder()
.with_tls(quic_tls)?
.with_limits(quic_limits)?
.with_io(tls_addr)?
.start()?;
let server_h3 = self.clone();
drop(tokio::spawn(async move {
let _ = server_h3.serve_h3(quic_server).await;
}));
}
let addr: std::net::SocketAddr = tls_addr
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
let tls_acceptor = Arc::new(tls_acceptor);
let redirect_addr: std::net::SocketAddr = cleartext_addr
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
let https_port = parse_port(tls_addr, 443);
run_worker_pool(
self,
addr,
Some((redirect_addr, https_port)),
move |server, listener| {
let tls_acceptor = tls_acceptor.clone();
async move { server.serve_https(listener, (*tls_acceptor).clone()).await }
},
)
.await?;
Ok(())
}
#[cfg(feature = "cert-gen")]
#[allow(clippy::too_many_lines)]
async fn start_all_inner(
self,
tls_addr: &str,
cleartext_addr: Option<&str>,
cert_pem: String,
key_pem: String,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls_pemfile::{certs, private_key};
enforce_fips_compliance()?;
let mut cert_reader = std::io::BufReader::new(cert_pem.as_bytes());
let cert_chain: Vec<CertificateDer<'static>> = certs(&mut cert_reader)
.filter_map(std::result::Result::ok)
.collect();
let mut key_reader = std::io::BufReader::new(key_pem.as_bytes());
let key_der: PrivateKeyDer<'static> = private_key(&mut key_reader)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to read private key: {e}"),
)
})?
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"No private key found in PEM",
)
})?;
let policy = self.effective_tls_policy();
let mut tls_config = rustls::ServerConfig::builder_with_provider(policy.provider())
.with_protocol_versions(policy.versions())
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to configure TLS protocol versions: {e}"),
)
})?
.with_no_client_auth()
.with_single_cert(cert_chain, key_der)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid certificate or key: {e}"),
)
})?;
#[cfg(feature = "http3")]
{
tls_config.alpn_protocols = alpn_protocols(true);
}
#[cfg(not(feature = "http3"))]
{
tls_config.alpn_protocols = alpn_protocols(false);
}
let tls_config = Arc::new(tls_config);
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
let https_port = parse_port(tls_addr, 443);
let redirect_info = if let Some(cleartext_addr) = cleartext_addr {
let redirect_addr: std::net::SocketAddr = cleartext_addr
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
Some((redirect_addr, https_port))
} else {
None
};
#[cfg(feature = "http3")]
{
let quic_tls = s2n_quic::provider::tls::rustls::Server::from(tls_config);
let quic_limits = s2n_quic::provider::limits::Limits::new()
.with_data_window(1_048_576)?
.with_bidirectional_local_data_window(1_048_576)?
.with_bidirectional_remote_data_window(1_048_576)?
.with_initial_round_trip_time(Duration::from_millis(100))?
.with_max_open_remote_bidirectional_streams(4096)?
.with_ack_elicitation_interval(4)?
.with_active_connection_migration(false)?
.with_max_active_connection_ids(2)?
.with_max_handshake_duration(Duration::from_secs(5))?;
let quic_server = s2n_quic::Server::builder()
.with_tls(quic_tls)?
.with_limits(quic_limits)?
.with_io(tls_addr)?
.start()?;
let server_h3 = self.clone();
drop(tokio::spawn(async move {
let _ = server_h3.serve_h3(quic_server).await;
}));
}
let addr: std::net::SocketAddr = tls_addr
.parse()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
let tls_acceptor = Arc::new(tls_acceptor);
run_worker_pool(self, addr, redirect_info, move |server, listener| {
let tls_acceptor = tls_acceptor.clone();
async move { server.serve_https(listener, (*tls_acceptor).clone()).await }
})
.await?;
Ok(())
}
}
#[allow(dead_code, clippy::unnecessary_wraps, clippy::missing_const_for_fn)]
pub(crate) fn enforce_fips_compliance() -> Result<(), std::io::Error> {
#[cfg(all(feature = "fips", feature = "tls"))]
{
if let Err(e) = aws_lc_rs::try_fips_mode() {
return Err(std::io::Error::other(format!(
"FIPS compliance check failed: {e}. Cryptographic backend is not in FIPS mode!"
)));
}
}
Ok(())
}
#[cfg(feature = "tls")]
pub(crate) fn alpn_protocols(include_h3: bool) -> Vec<Vec<u8>> {
let mut protocols = Vec::with_capacity(3);
if include_h3 {
protocols.push(b"h3".to_vec());
}
#[cfg(feature = "http2")]
protocols.push(b"h2".to_vec());
#[cfg(feature = "http1")]
protocols.push(b"http/1.1".to_vec());
protocols
}
#[cfg(any(feature = "cert-gen", feature = "lets-encrypt"))]
fn parse_port(addr: &str, default_port: u16) -> u16 {
addr.split(':')
.next_back()
.and_then(|p| p.parse::<u16>().ok())
.unwrap_or(default_port)
}
pub(crate) fn is_resource_exhaustion(e: &std::io::Error) -> bool {
matches!(e.raw_os_error(), Some(23 | 24 | 10024))
}
#[cfg(feature = "tls")]
pub async fn serve_http_redirect_and_challenges(listener: TcpListener, https_port: u16) {
let builder =
hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
loop {
let (stream, _peer) = match listener.accept().await {
Ok(c) => c,
Err(e) => {
tracing::error!("[http-redirect] Accept error: {e}");
if is_resource_exhaustion(&e) {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
continue;
}
};
let _ = stream.set_nodelay(true);
let io = hyper_util::rt::TokioIo::new(stream);
let builder = builder.clone();
drop(tokio::spawn(async move {
let _ = builder
.serve_connection(
io,
service_fn(move |req: Request<hyper::body::Incoming>| {
#[allow(unused_variables)]
let path = req.uri().path().to_owned();
async move {
#[cfg(feature = "lets-encrypt")]
if let Some(token) = path.strip_prefix("/.well-known/acme-challenge/")
&& let Some(key_auth) = crate::tls::acme::get_challenge(token)
{
let resp = Response::builder()
.status(200)
.header("content-type", "text/plain")
.body(Body::full(bytes::Bytes::from(key_auth)))
.unwrap_or_else(|_| Response::new(Body::empty()));
return Ok::<_, std::convert::Infallible>(resp);
}
let host = req
.headers()
.get("host")
.and_then(|h| h.to_str().ok())
.unwrap_or("localhost");
let host_no_port = host.split(':').next().unwrap_or("localhost");
let port_suffix = if https_port == 443 {
String::new()
} else {
format!(":{https_port}")
};
let path_and_query = req
.uri()
.path_and_query()
.map_or("/", hyper::http::uri::PathAndQuery::as_str);
let location =
format!("https://{host_no_port}{port_suffix}{path_and_query}");
let resp = Response::builder()
.status(308) .header("location", &location)
.body(Body::empty())
.unwrap_or_else(|_| Response::new(Body::empty()));
Ok::<_, std::convert::Infallible>(resp)
}
}),
)
.await;
}));
}
}
pub async fn serve(
listener: tokio::net::TcpListener,
router: crate::routing::Router<()>,
) -> Result<(), std::io::Error> {
let addr = listener.local_addr()?;
drop(listener);
let server = Server::new(router);
server.start_http_addr(addr).await
}
#[cfg(feature = "tls")]
#[derive(Clone)]
pub struct RustlsConfig {
pub(crate) server_config: Arc<rustls::ServerConfig>,
}
#[cfg(feature = "tls")]
impl std::fmt::Debug for RustlsConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RustlsConfig").finish_non_exhaustive()
}
}
#[cfg(feature = "tls")]
impl RustlsConfig {
#[allow(clippy::unused_async)]
pub async fn from_pem(cert: Vec<u8>, key: Vec<u8>) -> Result<Self, std::io::Error> {
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls_pemfile::{certs, private_key};
let mut cert_reader = std::io::BufReader::new(cert.as_slice());
let cert_chain: Vec<CertificateDer<'static>> = certs(&mut cert_reader)
.filter_map(std::result::Result::ok)
.collect();
let mut key_reader = std::io::BufReader::new(key.as_slice());
let key_der: PrivateKeyDer<'static> = private_key(&mut key_reader)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to read private key: {e}"),
)
})?
.ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::NotFound, "No private key found in PEM")
})?;
let mut server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, key_der)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
server_config.alpn_protocols = alpn_protocols(false);
Ok(Self {
server_config: Arc::new(server_config),
})
}
}
#[cfg(feature = "tls")]
#[must_use]
pub const fn bind_rustls(addr: std::net::SocketAddr, config: RustlsConfig) -> HttpsServer {
HttpsServer {
addr,
config,
serve_http3: false,
}
}
#[cfg(feature = "tls")]
pub struct HttpsServer {
addr: std::net::SocketAddr,
config: RustlsConfig,
serve_http3: bool,
}
#[cfg(feature = "tls")]
impl std::fmt::Debug for HttpsServer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HttpsServer")
.field("addr", &self.addr)
.field("serve_http3", &self.serve_http3)
.finish_non_exhaustive()
}
}
#[cfg(feature = "tls")]
impl HttpsServer {
#[must_use]
pub const fn serve_http3(mut self, enable: bool) -> Self {
self.serve_http3 = enable;
self
}
pub async fn serve(self, router: crate::routing::Router<()>) -> Result<(), std::io::Error> {
let server = Server::new(router);
#[cfg_attr(not(feature = "http3"), allow(unused_mut))]
let mut rustls_config = (*self.config.server_config).clone();
#[cfg(feature = "http3")]
if self.serve_http3 {
if !rustls_config.alpn_protocols.iter().any(|p| p == b"h3") {
rustls_config.alpn_protocols.insert(0, b"h3".to_vec());
}
let tls_config_arc = Arc::new(rustls_config.clone());
let quic_tls = s2n_quic::provider::tls::rustls::Server::from(tls_config_arc);
let quic_limits = s2n_quic::provider::limits::Limits::new()
.with_data_window(1_048_576)
.map_err(std::io::Error::other)?
.with_bidirectional_local_data_window(1_048_576)
.map_err(std::io::Error::other)?
.with_bidirectional_remote_data_window(1_048_576)
.map_err(std::io::Error::other)?
.with_initial_round_trip_time(Duration::from_millis(100))
.map_err(std::io::Error::other)?
.with_max_open_remote_bidirectional_streams(4096)
.map_err(std::io::Error::other)?
.with_ack_elicitation_interval(4)
.map_err(std::io::Error::other)?
.with_active_connection_migration(false)
.map_err(std::io::Error::other)?
.with_max_active_connection_ids(2)
.map_err(std::io::Error::other)?
.with_max_handshake_duration(Duration::from_secs(5))
.map_err(std::io::Error::other)?;
let quic_server = s2n_quic::Server::builder()
.with_tls(quic_tls)
.map_err(std::io::Error::other)?
.with_limits(quic_limits)
.map_err(std::io::Error::other)?
.with_io(self.addr)
.map_err(std::io::Error::other)?
.start()
.map_err(std::io::Error::other)?;
let server_h3 = server.clone();
tokio::spawn(async move {
let _ = server_h3.serve_h3(quic_server).await;
});
}
server
.start_https_with_config_addr(self.addr, rustls_config)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::routing::Router;
#[test]
#[allow(clippy::redundant_clone)] fn clone_preserves_body_size_and_max_connections() {
let server = Server::new(Router::new())
.max_body_size(4096)
.max_connections(7);
let cloned = server.clone();
assert_eq!(cloned.max_body_size, 4096);
assert_eq!(cloned.max_connections, 7);
}
#[cfg(feature = "tls")]
#[test]
#[allow(clippy::redundant_clone)] fn clone_preserves_tls_policy() {
let server =
Server::new(Router::new()).tls_policy(crate::tls::TlsPolicy::hardened().tls13_only());
assert!(server.tls_policy.is_some());
let cloned = server.clone();
assert!(cloned.tls_policy.is_some());
}
#[test]
fn max_connections_builder_sets_field() {
let server = Server::new(Router::new()).max_connections(42);
assert_eq!(server.max_connections, 42);
assert_eq!(server.max_body_size, 2 * 1024 * 1024);
}
#[test]
fn is_resource_exhaustion_matches_known_codes() {
assert!(is_resource_exhaustion(&std::io::Error::from_raw_os_error(
24
)));
assert!(is_resource_exhaustion(&std::io::Error::from_raw_os_error(
23
)));
assert!(is_resource_exhaustion(&std::io::Error::from_raw_os_error(
10024
)));
}
#[test]
fn is_resource_exhaustion_false_for_unrelated_errors() {
assert!(!is_resource_exhaustion(&std::io::Error::from_raw_os_error(
2
)));
assert!(!is_resource_exhaustion(&std::io::Error::other(
"not an os error"
)));
}
#[cfg(feature = "tls")]
#[test]
fn rustls_config_debug_smoke() {
let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
.expect("generate self-signed cert");
let mut server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert.cert_der], cert.key_der)
.expect("build server config");
server_config.alpn_protocols = alpn_protocols(false);
let config = RustlsConfig {
server_config: Arc::new(server_config),
};
let dbg = format!("{config:?}");
assert!(dbg.contains("RustlsConfig"));
}
#[cfg(feature = "tls")]
#[tokio::test]
async fn rustls_config_from_pem_rejects_garbage_input() {
let err = RustlsConfig::from_pem(b"not a cert".to_vec(), b"not a key".to_vec())
.await
.expect_err("garbage PEM must not build a config");
assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
}
#[cfg(feature = "tls")]
#[tokio::test]
async fn rustls_config_from_pem_builds_from_a_valid_self_signed_cert() {
let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
.expect("generate self-signed cert");
let config = RustlsConfig::from_pem(cert.cert_pem.into_bytes(), cert.key_pem.into_bytes())
.await
.expect("build config from valid PEM");
assert!(!config.server_config.alpn_protocols.is_empty());
}
#[cfg(feature = "tls")]
#[test]
fn bind_rustls_and_https_server_builders() {
let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
.expect("generate self-signed cert");
let mut server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![cert.cert_der], cert.key_der)
.expect("build server config");
server_config.alpn_protocols = alpn_protocols(false);
let config = RustlsConfig {
server_config: Arc::new(server_config),
};
let addr: std::net::SocketAddr = "127.0.0.1:0".parse().expect("parse addr");
let https_server = bind_rustls(addr, config);
assert_eq!(https_server.addr, addr);
assert!(!https_server.serve_http3);
let dbg = format!("{https_server:?}");
assert!(dbg.contains("HttpsServer"));
assert!(dbg.contains("serve_http3: false"));
let https_server = https_server.serve_http3(true);
assert!(https_server.serve_http3);
let dbg = format!("{https_server:?}");
assert!(dbg.contains("serve_http3: true"));
}
}