Skip to main content

tachyon_web/server/
mod.rs

1//! High-performance Web Server Engine supporting HTTP/1.1, HTTP/2, and HTTP/3.
2//!
3//! The `server` module provides the [`Server`] struct, which wraps a [`CompiledRouter`] and
4//! dispatches incoming network streams for all supported HTTP versions.
5//!
6//! # Protocol support
7//!
8//! | Method | Protocol | Feature flag |
9//! |---|---|---|
10//! | [`serve_http`] | HTTP/1.1 plain TCP (+ HTTP/2 cleartext "h2c" with `http2`) | *(always)* |
11//! | [`serve_https`] | HTTP/1.1 + HTTP/2 over TLS | `tls` |
12//! | [`serve_https_config`] | Same but with custom `ServerConfig` | `tls` |
13//! | [`serve_h3`] | HTTP/3 over QUIC | `http3` |
14//! | [`start_all`] | All of the above via PEM cert/key strings | `cert-gen` |
15//! | [`serve_all_acme`] | All of the above, certs managed by Let's Encrypt | `lets-encrypt` |
16//! | [`serve_tor`] | HTTP/1.1 (+ h2c) over a native Tor `.onion` hidden service | `tor` |
17//! | [`serve_i2p`] | HTTP/1.1 (+ h2c) over a native I2P `.b32.i2p` eepsite ([⚠️ breaks `forbid(unsafe_code)`](i2p)) | `i2p` |
18//!
19//! [`serve_http`]: Server::serve_http
20//! [`serve_https`]: Server::serve_https
21//! [`serve_https_config`]: Server::serve_https_config
22//! [`serve_h3`]: Server::serve_h3
23//! [`start_all`]: Server::start_all
24//! [`serve_all_acme`]: Server::serve_all_acme
25//! [`serve_tor`]: Server::serve_tor
26//! [`serve_i2p`]: Server::serve_i2p
27//! [`CompiledRouter`]: crate::routing::CompiledRouter
28//!
29//! # Publishing over more than one transport at once
30//!
31//! Each `serve_*` method above consumes its `Server` and blocks for that one transport's
32//! lifetime — the right building block for a single-transport deployment. To publish the same
33//! app over **several** transports at once (e.g. clearnet HTTPS *and* a `.onion` mirror *and*
34//! a `.i2p` mirror, all from one process), prefer [`MultiServer`] over hand-rolling
35//! `tokio::spawn` + `tokio::select!` around the individual `serve_*` calls yourself — it owns
36//! exactly that boilerplate:
37//!
38//! ```rust,no_run
39//! # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
40//! use tachyon_web::{Router, Server, get};
41//!
42//! let app: Router = Router::new().route("/", get(|| async { "hi" }));
43//! let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
44//!
45//! Server::new(app)
46//!     .with_http(listener)
47//!     // .with_onion(onion_config)   // requires the `tor` feature
48//!     // .with_i2p(i2p_config)       // requires the `i2p` feature
49//!     .serve()
50//!     .await?;
51//! # Ok(())
52//! # }
53//! ```
54
55#[cfg(any(feature = "tor", feature = "i2p"))]
56pub(crate) mod conn;
57#[cfg(feature = "http3")]
58mod h3;
59mod http;
60#[cfg(feature = "i2p")]
61pub mod i2p;
62mod multi;
63#[cfg(feature = "tor")]
64pub mod tor;
65
66pub use multi::MultiServer;
67
68use crate::routing::CompiledRouter;
69use std::future::Future;
70use std::sync::Arc;
71use std::time::Duration;
72use tokio::net::TcpListener;
73
74#[cfg(feature = "tls")]
75use crate::http::response::Body;
76#[cfg(feature = "tls")]
77use hyper::service::service_fn;
78#[cfg(feature = "tls")]
79use hyper::{Request, Response};
80#[cfg(any(feature = "cert-gen", feature = "lets-encrypt", feature = "http3"))]
81use tokio_rustls::TlsAcceptor;
82
83/// Default read timeout for both plaintext and TLS connections.
84pub(crate) const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
85/// Default handshake timeout for TLS connections.
86#[cfg(feature = "tls")]
87pub(crate) const TLS_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(3);
88/// How long [`Server::serve_all_acme`] waits for the first certificate to be
89/// cached or provisioned before starting the TLS listener regardless.
90#[cfg(feature = "lets-encrypt")]
91const FIRST_CERT_TIMEOUT: Duration = Duration::from_secs(30);
92
93thread_local! {
94    pub(crate) static IS_LOCAL_WORKER: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
95}
96
97/// Samples a delay uniformly from `[min, max)` for [`Server::response_jitter`].
98///
99/// Not a CSPRNG — this only needs enough variance to blunt naive response-time
100/// correlation, not to resist an adversary who can influence the seed, so a
101/// `RandomState` hasher (itself seeded from the OS's own random source at
102/// construction) reseeded with the current time is sufficient without pulling in a
103/// dedicated `rand` dependency for one call site.
104pub(crate) fn jittered_delay(min: Duration, max: Duration) -> Duration {
105    use std::hash::{BuildHasher, Hasher};
106
107    if max <= min {
108        return min;
109    }
110    let mut hasher = std::collections::hash_map::RandomState::new().build_hasher();
111    let now_nanos = std::time::SystemTime::now()
112        .duration_since(std::time::UNIX_EPOCH)
113        .map_or(0, |d| d.as_nanos());
114    hasher.write_u128(now_nanos);
115    let span_nanos = max.checked_sub(min).unwrap_or(max).as_nanos().max(1);
116    let offset_nanos = (u128::from(hasher.finish()) % span_nanos).min(u128::from(u64::MAX));
117    min + Duration::from_nanos(u64::try_from(offset_nanos).unwrap_or(u64::MAX))
118}
119
120fn bind_reuseport(addr: std::net::SocketAddr) -> Result<std::net::TcpListener, std::io::Error> {
121    use socket2::{Domain, Protocol, Socket, Type};
122    let domain = Domain::for_address(addr);
123    let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
124    socket.set_reuse_address(true)?;
125    #[cfg(unix)]
126    {
127        socket.set_reuse_port(true)?;
128    }
129    socket.bind(&addr.into())?;
130    socket.set_nonblocking(true)?;
131    socket.listen(4096)?;
132    Ok(std::net::TcpListener::from(socket))
133}
134
135async fn run_worker_pool<S, F, Fut>(
136    server: Server<S>,
137    addr: std::net::SocketAddr,
138    redirect_info: Option<(std::net::SocketAddr, u16)>,
139    serve_fn: F,
140) -> Result<(), std::io::Error>
141where
142    S: Clone + Send + Sync + 'static,
143    F: Fn(Server<S>, TcpListener) -> Fut + Send + Sync + 'static,
144    Fut: Future<Output = Result<(), std::io::Error>> + Send + 'static,
145{
146    let cores = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
147    let core_ids = core_affinity::get_core_ids().unwrap_or_default();
148    let mut handles = Vec::new();
149    let server = Arc::new(server);
150    let serve_fn = Arc::new(serve_fn);
151    // Each worker thread reports whether it managed to bind its listener, so
152    // that a totally unbindable address (e.g. permission denied, or already
153    // in use on every core) produces a real `Err` instead of hanging forever
154    // on the `pending()` below with only a log line to show for it.
155    let (bind_tx, bind_rx) = std::sync::mpsc::channel::<Result<(), std::io::Error>>();
156
157    for i in 0..cores {
158        let server = server.clone();
159        let serve_fn = serve_fn.clone();
160        let core_id = core_ids.get(i).copied();
161        let bind_tx = bind_tx.clone();
162        let handle = std::thread::Builder::new()
163            .name(format!("tachyon-worker-{i}"))
164            .stack_size(512 * 1024)
165            .spawn(move || {
166                if let Some(id) = core_id {
167                    let _ = core_affinity::set_for_current(id);
168                }
169
170                let Ok(rt) = tokio::runtime::Builder::new_current_thread()
171                    .enable_all()
172                    .build()
173                else {
174                    tracing::error!("Failed to build Tokio runtime for worker thread");
175                    return;
176                };
177
178                let local = tokio::task::LocalSet::new();
179                local.block_on(&rt, async move {
180                    IS_LOCAL_WORKER.with(|flag| flag.set(true));
181
182                    // Only used to bind the HTTP->HTTPS redirect listener, which only
183                    // exists when TLS is enabled — kept alive here so the parameter
184                    // isn't flagged as unused in non-`tls` builds.
185                    #[cfg(not(feature = "tls"))]
186                    let _ = &redirect_info;
187
188                    #[cfg(feature = "tls")]
189                    if let Some((r_addr, https_port)) = redirect_info {
190                        let r_listener_res = bind_reuseport(r_addr).and_then(TcpListener::from_std);
191                        match r_listener_res {
192                            Ok(l) => {
193                                tokio::task::spawn_local(async move {
194                                    serve_http_redirect_and_challenges(l, https_port).await;
195                                });
196                            }
197                            Err(e) => {
198                                tracing::error!("Worker redirect bind error: {e}");
199                            }
200                        }
201                    }
202
203                    let listener_res = bind_reuseport(addr).and_then(TcpListener::from_std);
204                    let listener = match listener_res {
205                        Ok(l) => {
206                            let _ = bind_tx.send(Ok(()));
207                            l
208                        }
209                        Err(e) => {
210                            tracing::error!("Worker bind error: {e}");
211                            let _ = bind_tx.send(Err(e));
212                            return;
213                        }
214                    };
215
216                    let server_clone = (*server).clone();
217
218                    let _ = serve_fn(server_clone, listener).await;
219                });
220            })?;
221        handles.push(handle);
222    }
223    drop(bind_tx);
224
225    let bind_results = tokio::task::spawn_blocking(move || {
226        (0..cores).filter_map(|_| bind_rx.recv().ok()).collect::<Vec<_>>()
227    })
228    .await
229    .unwrap_or_default();
230    let bound = bind_results.iter().filter(|r| r.is_ok()).count();
231    if bound == 0 {
232        return Err(bind_results
233            .into_iter()
234            .find_map(std::result::Result::err)
235            .unwrap_or_else(|| {
236                std::io::Error::other("all worker threads failed to bind their listener")
237            }));
238    }
239    if bound < cores {
240        tracing::warn!(
241            "Only {bound}/{cores} worker threads bound successfully; running in a degraded state"
242        );
243    }
244
245    let _ = handles;
246    std::future::pending::<()>().await;
247    Ok(())
248}
249
250// ─── Server ──────────────────────────────────────────────────────────────────
251
252/// Main server configuration and runner.
253///
254/// Wraps a [`CompiledRouter`] and provides multiple `serve_*` methods for different
255/// transport protocols. The server is cheaply cloneable via `Arc` internally.
256///
257/// # Example
258///
259/// ```rust,no_run
260/// use tachyon_web::{Router, Server, get};
261/// use tokio::net::TcpListener;
262///
263/// async fn hello() -> &'static str { "hello" }
264///
265/// #[tokio::main]
266/// async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
267///     let app = Router::new().route("/", get(hello));
268///     let listener = TcpListener::bind("0.0.0.0:8080").await?;
269///     Server::new(app).serve_http(listener).await?;
270///     Ok(())
271/// }
272/// ```
273#[derive(Debug)]
274pub struct Server<S> {
275    pub(crate) router: CompiledRouter<S>,
276    /// Maximum permitted request body size in bytes (default: 2 MiB, matching
277    /// Axum's `DefaultBodyLimit` default).
278    pub max_body_size: usize,
279    /// Maximum number of concurrent active TCP connections **per worker thread**.
280    ///
281    /// Tachyon runs one worker (with its own `SO_REUSEPORT` listener and connection
282    /// semaphore) per CPU core, so the effective process-wide ceiling is
283    /// `max_connections × number of cores`, not a single global cap. Size this
284    /// accordingly if you're relying on it for downstream resource planning (e.g.
285    /// a connection-pooled database sized to the server's max concurrency).
286    ///
287    /// This per-core sharding applies to [`serve_http`] and [`serve_https`]
288    /// (and anything built on them, like [`serve_all_acme`]). HTTP/3
289    /// ([`serve_h3`]) runs a single QUIC endpoint with its own connection
290    /// semaphore, not sharded across the worker pool — for H3 traffic the
291    /// effective ceiling is `max_connections` alone.
292    ///
293    /// [`serve_http`]: Server::serve_http
294    /// [`serve_https`]: Server::serve_https
295    /// [`serve_all_acme`]: Server::serve_all_acme
296    /// [`serve_h3`]: Server::serve_h3
297    ///
298    /// Default: 25,600 — matching `actix-server`'s own per-worker
299    /// `max_concurrent_connections`.
300    pub max_connections: usize,
301    /// Crypto/TLS policy shared across every listener this `Server` runs — see
302    /// [`Server::tls_policy`]. `None` means each listener falls back to
303    /// [`TlsPolicy::hardened`](crate::tls::TlsPolicy::hardened).
304    #[cfg(feature = "tls")]
305    pub(crate) tls_policy: Option<crate::tls::TlsPolicy>,
306    /// Random per-response delay range added before every response is returned — see
307    /// [`Server::response_jitter`]. `None` (the default) adds no delay.
308    pub(crate) response_jitter: Option<(Duration, Duration)>,
309}
310
311impl<S> Clone for Server<S>
312where
313    S: Clone,
314{
315    fn clone(&self) -> Self {
316        Self {
317            router: self.router.clone(),
318            max_body_size: self.max_body_size,
319            max_connections: self.max_connections,
320            #[cfg(feature = "tls")]
321            tls_policy: self.tls_policy.clone(),
322            response_jitter: self.response_jitter,
323        }
324    }
325}
326
327impl Server<()> {
328    /// Creates a new `Server` with default settings and the given router.
329    ///
330    /// # Panics
331    /// Panics if router compilation fails (e.g. a duplicate route was registered).
332    #[must_use]
333    #[allow(clippy::expect_used)]
334    pub fn new(router: crate::routing::Router<()>) -> Self {
335        let compiled = router.compile().expect("Router compilation failed");
336        Self {
337            router: compiled,
338            max_body_size: 2 * 1024 * 1024, // 2 MiB (matches Axum's `DefaultBodyLimit` default)
339            max_connections: 25_600,
340            #[cfg(feature = "tls")]
341            tls_policy: None,
342            response_jitter: None,
343        }
344    }
345}
346
347impl<S> Server<S>
348where
349    S: Clone + Send + Sync + 'static,
350{
351    /// Overrides the maximum request body size (in bytes).
352    ///
353    /// Requests whose body exceeds this limit are rejected with `413 Content Too Large`
354    /// before the body bytes are fully buffered. The default is **2 MiB**, matching
355    /// Axum's `DefaultBodyLimit` default.
356    ///
357    /// # Example
358    /// ```rust,no_run
359    /// # use tachyon_web::{Router, Server};
360    /// # let router = Router::new();
361    /// let server = Server::new(router).max_body_size(64 * 1024 * 1024); // 64 MiB
362    /// ```
363    #[must_use]
364    pub const fn max_body_size(mut self, size: usize) -> Self {
365        self.max_body_size = size;
366        self
367    }
368
369    /// Overrides the maximum number of concurrent connections **per worker thread**
370    /// (default: 25,600 — see [`Server::max_connections`] for why this isn't a
371    /// single process-wide cap).
372    #[must_use]
373    pub const fn max_connections(mut self, limit: usize) -> Self {
374        self.max_connections = limit;
375        self
376    }
377
378    /// Adds a random delay, uniformly sampled from `[min, max)`, before every response this
379    /// `Server` returns — on every transport it serves (clearnet, `.onion`, `.i2p` alike, since
380    /// they all funnel through the same response path).
381    ///
382    /// Off by default. This exists to blunt naive **response-time correlation**: if you run the
383    /// same app on both clearnet and a `.onion`/`.i2p` mirror (e.g. via [`MultiServer`]), an
384    /// observer positioned to time both could otherwise try to match requests between them by
385    /// how long the handler took to respond. Jitter alone does not make correlation impossible —
386    /// it raises the number of samples an observer needs, nothing more — so treat it as one
387    /// layer among several (network-level timing is a much stronger signal than this addresses),
388    /// not a complete mitigation.
389    ///
390    /// `min == max` (or `max <= min`) always waits exactly `min` — use this for a fixed
391    /// per-response delay instead of a random range.
392    #[must_use]
393    pub const fn response_jitter(mut self, min: Duration, max: Duration) -> Self {
394        self.response_jitter = Some((min, max));
395        self
396    }
397
398    /// Sets a custom `rustls::crypto::CryptoProvider` to be used for TLS operations.
399    ///
400    /// This overrides the default provider (which uses `aws-lc-rs` with customized Kex and AEAD).
401    /// Shorthand for `.tls_policy(TlsPolicy::with_provider(provider))` — use
402    /// [`tls_policy`](Self::tls_policy) directly if you also want to restrict protocol
403    /// versions (e.g. TLS 1.3-only) or install this provider process-wide for arti's Tor
404    /// relay connections.
405    #[cfg(feature = "tls")]
406    #[must_use]
407    pub fn crypto_provider(self, provider: Arc<rustls::crypto::CryptoProvider>) -> Self {
408        self.tls_policy(crate::tls::TlsPolicy::with_provider(provider))
409    }
410
411    /// Sets the crypto/TLS policy shared by every listener this `Server` runs: clearnet HTTPS
412    /// (static cert or Let's Encrypt), the onion `.onion` HTTPS termination, and the I2P
413    /// eepsite's optional TLS layer. All three derive their `rustls::ServerConfig` (including
414    /// self-signed certs) from the same [`TlsPolicy`](crate::tls::TlsPolicy) instead of each
415    /// reconstructing their own defaults.
416    ///
417    /// Defaults to [`TlsPolicy::hardened`](crate::tls::TlsPolicy::hardened) if never called.
418    ///
419    /// See [`TlsPolicy`](crate::tls::TlsPolicy)'s docs for how this interacts with Tor's
420    /// relay/channel TLS layer (a separate concern from HTTPS termination).
421    #[cfg(feature = "tls")]
422    #[must_use]
423    pub fn tls_policy(mut self, policy: crate::tls::TlsPolicy) -> Self {
424        self.tls_policy = Some(policy);
425        self
426    }
427
428    /// Returns the effective [`TlsPolicy`](crate::tls::TlsPolicy) for this server: the one set
429    /// via [`tls_policy`](Self::tls_policy)/[`crypto_provider`](Self::crypto_provider), or
430    /// [`TlsPolicy::hardened`](crate::tls::TlsPolicy::hardened) if neither was called.
431    ///
432    /// Only consumed by the entry points that actually build a `rustls::ServerConfig`
433    /// themselves — or, for `tor`, that install this policy's provider as rustls's
434    /// process-wide default before bootstrapping (see `TlsPolicy`'s docs): `start_all`/
435    /// `start_all_inner` (`cert-gen`), `serve_all_acme` (`lets-encrypt`, which implies
436    /// `cert-gen`), `Server::serve_tor`/`serve_onion` (`tor` + `tls`, for the process-wide
437    /// install — the plaintext-only `_with_client` variants never call this since they don't
438    /// own the bootstrap), and the onion/i2p self-signed-cert paths in `server/tor.rs`/
439    /// `server/i2p.rs` (both require `cert-gen`, already covered by that disjunct — a
440    /// caller-supplied `OnionTls::Custom`/`I2pTls::Custom` config, needing only `tls`, doesn't
441    /// call this at all). Gated the same way so builds that don't actually reach any of these
442    /// paths don't trip `-D dead-code`.
443    #[cfg(any(
444        feature = "cert-gen",
445        feature = "lets-encrypt",
446        all(feature = "tor", feature = "tls"),
447    ))]
448    pub(crate) fn effective_tls_policy(&self) -> crate::tls::TlsPolicy {
449        self.tls_policy.clone().unwrap_or_default()
450    }
451
452    /// Begins publishing this app over **multiple transports at once** — see
453    /// [`MultiServer`] and the [module docs](self#publishing-over-more-than-one-transport-at-once).
454    ///
455    /// Adds a plaintext clearnet HTTP transport bound to `listener`; chain more `.with_*` calls
456    /// (`.with_https`/`.with_h3`/`.with_onion`/`.with_i2p`) to add further transports, then
457    /// finish with `.serve().await`.
458    pub fn with_http(self, listener: TcpListener) -> MultiServer<S> {
459        MultiServer::new(self).with_http(listener)
460    }
461
462    /// Begins publishing this app over **multiple transports at once** — see
463    /// [`MultiServer`] and the [module docs](self#publishing-over-more-than-one-transport-at-once).
464    ///
465    /// Adds a clearnet HTTPS transport bound to `listener`, terminated with `config`. Requires
466    /// the `tls` feature.
467    #[cfg(feature = "tls")]
468    pub fn with_https(self, listener: TcpListener, config: rustls::ServerConfig) -> MultiServer<S> {
469        MultiServer::new(self).with_https(listener, config)
470    }
471
472    /// Begins publishing this app over **multiple transports at once** — see
473    /// [`MultiServer`] and the [module docs](self#publishing-over-more-than-one-transport-at-once).
474    ///
475    /// Adds an HTTP/3-over-QUIC transport. Requires the `http3` feature.
476    #[cfg(feature = "http3")]
477    pub fn with_h3(self, quic_server: s2n_quic::Server) -> MultiServer<S> {
478        MultiServer::new(self).with_h3(quic_server)
479    }
480
481    /// Begins publishing this app over **multiple transports at once** — see
482    /// [`MultiServer`] and the [module docs](self#publishing-over-more-than-one-transport-at-once).
483    ///
484    /// Adds a Tor `.onion` hidden-service transport. Requires the `tor` feature.
485    #[cfg(feature = "tor")]
486    pub fn with_onion(self, config: tor::OnionConfig) -> MultiServer<S> {
487        MultiServer::new(self).with_onion(config)
488    }
489
490    /// Begins publishing this app over **multiple transports at once** — see
491    /// [`MultiServer`] and the [module docs](self#publishing-over-more-than-one-transport-at-once).
492    ///
493    /// Adds an I2P `.b32.i2p` eepsite transport. Requires the `i2p` feature
494    /// ([⚠️ breaks `forbid(unsafe_code)`](i2p)).
495    #[cfg(feature = "i2p")]
496    pub fn with_i2p(self, config: i2p::I2pConfig) -> MultiServer<S> {
497        MultiServer::new(self).with_i2p(config)
498    }
499
500    /// Starts a pure plaintext HTTP server on a parsed `SocketAddr`.
501    ///
502    /// # Errors
503    ///
504    /// Returns an error if the server fails to run.
505    pub async fn start_http_addr(self, addr: std::net::SocketAddr) -> Result<(), std::io::Error> {
506        run_worker_pool(self, addr, None, |server, listener| async move {
507            server.serve_http(listener).await
508        })
509        .await
510    }
511
512    /// Starts a pure plaintext HTTP server.
513    ///
514    /// # Arguments
515    /// - `http_addr`: The address to bind (e.g., `"0.0.0.0:80"`).
516    ///
517    /// # Errors
518    ///
519    /// Returns an error if parsing the bind address fails or the server fails to run.
520    pub async fn start_http(self, http_addr: &str) -> Result<(), std::io::Error> {
521        let addr: std::net::SocketAddr = http_addr
522            .parse()
523            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
524        self.start_http_addr(addr).await
525    }
526
527    /// Starts a pure HTTPS server (HTTP/1.1 and HTTP/2 over TLS) using a custom `rustls::ServerConfig`.
528    ///
529    /// This provides advanced control for users who want to configure TLS themselves,
530    /// without relying on `cert-gen` or Let's Encrypt automation.
531    ///
532    /// # Arguments
533    /// - `tls_addr`: The address to bind for TLS (e.g., `"0.0.0.0:443"`).
534    /// - `config`: A configured `rustls::ServerConfig`.
535    ///
536    /// # Errors
537    ///
538    /// Returns an error if parsing the bind address fails or the server fails to run.
539    #[cfg(feature = "tls")]
540    pub async fn start_https_with_config_addr(
541        self,
542        addr: std::net::SocketAddr,
543        config: rustls::ServerConfig,
544    ) -> Result<(), std::io::Error> {
545        let config = Arc::new(config);
546        run_worker_pool(self, addr, None, move |server, listener| {
547            let config = config.clone();
548            async move { server.serve_https_config(listener, (*config).clone()).await }
549        })
550        .await
551    }
552
553    /// Starts a pure HTTPS server (HTTP/1.1 and HTTP/2 over TLS) using a custom `rustls::ServerConfig`.
554    ///
555    /// This provides advanced control for users who want to configure TLS themselves,
556    /// without relying on `cert-gen` or Let's Encrypt automation.
557    ///
558    /// # Arguments
559    /// - `tls_addr`: The address to bind for TLS (e.g., `"0.0.0.0:443"`).
560    /// - `config`: A configured `rustls::ServerConfig`.
561    ///
562    /// # Errors
563    ///
564    /// Returns an error if parsing the bind address fails or the server fails to run.
565    #[cfg(feature = "tls")]
566    pub async fn start_https_with_config(
567        self,
568        tls_addr: &str,
569        config: rustls::ServerConfig,
570    ) -> Result<(), std::io::Error> {
571        let addr: std::net::SocketAddr = tls_addr
572            .parse()
573            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
574        self.start_https_with_config_addr(addr, config).await
575    }
576
577    /// Starts HTTPS (HTTP/1.1 + HTTP/2 over TLS) and HTTP/3 (QUIC) using a custom `rustls::ServerConfig`.
578    ///
579    /// This provides advanced control for users who want to configure TLS themselves,
580    /// without relying on `cert-gen` or Let's Encrypt automation. Both listeners will bind
581    /// to the provided `tls_addr` (TCP for HTTPS and UDP for HTTP/3).
582    ///
583    /// # Arguments
584    /// - `tls_addr`: The address to bind for TCP and UDP (e.g., `"0.0.0.0:443"`).
585    /// - `config`: A configured `rustls::ServerConfig`.
586    ///
587    /// # Errors
588    ///
589    /// Returns an error if FIPS compliance enforcement, binding, or server initialization fails.
590    #[cfg(all(feature = "tls", feature = "http3"))]
591    pub async fn start_https_and_h3_with_config(
592        self,
593        tls_addr: &str,
594        mut config: rustls::ServerConfig,
595    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
596        enforce_fips_compliance()?;
597
598        // Ensure ALPN includes HTTP/3 and standard HTTP/2 / HTTP/1.1
599        config.alpn_protocols = alpn_protocols(true);
600        let config = Arc::new(config);
601
602        // Start HTTP/3 QUIC Server
603        let quic_tls = s2n_quic::provider::tls::rustls::Server::from(config.clone());
604        let quic_limits = s2n_quic::provider::limits::Limits::new()
605            // 1 MB flow-control windows match H/2 settings and saturate LAN pipes.
606            .with_data_window(1_048_576)?
607            .with_bidirectional_local_data_window(1_048_576)?
608            .with_bidirectional_remote_data_window(1_048_576)?
609            // Tuning: 100ms is a safe and standard default initial RTT for public internet clients.
610            .with_initial_round_trip_time(Duration::from_millis(100))?
611            // More simultaneous streams per connection.
612            .with_max_open_remote_bidirectional_streams(4096)?
613            // Keep ACK overhead low: ACK every 4th packet (default is every 2nd).
614            .with_ack_elicitation_interval(4)?
615            // Disable active migration for server-side benchmarks (saves state tracking).
616            .with_active_connection_migration(false)?
617            // Reduce connection-ID slots (fewer is fine for 0-RTT / stationary peers).
618            .with_max_active_connection_ids(2)?
619            // Aggressive handshake timeout: reject slow clients quickly.
620            .with_max_handshake_duration(Duration::from_secs(5))?;
621        let quic_server = s2n_quic::Server::builder()
622            .with_tls(quic_tls)?
623            .with_limits(quic_limits)?
624            .with_io(tls_addr)?
625            .start()?;
626
627        let server_h3 = self.clone();
628        drop(tokio::spawn(async move {
629            let _ = server_h3.serve_h3(quic_server).await;
630        }));
631
632        // Start HTTPS Server
633        let addr: std::net::SocketAddr = tls_addr
634            .parse()
635            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
636        let tls_acceptor = TlsAcceptor::from(config);
637        let tls_acceptor = Arc::new(tls_acceptor);
638        run_worker_pool(self, addr, None, move |server, listener| {
639            let tls_acceptor = tls_acceptor.clone();
640            async move { server.serve_https(listener, (*tls_acceptor).clone()).await }
641        })
642        .await?;
643
644        Ok(())
645    }
646
647    /// Starts the server across **all enabled protocols simultaneously** using
648    /// pre-loaded PEM certificate and key strings.
649    ///
650    /// This is a convenience wrapper that sets up:
651    /// - **HTTP → HTTPS redirect** on `cleartext_addr` (if provided), with ACME HTTP-01
652    ///   challenge pass-through so Let's Encrypt can validate the domain even while
653    ///   this server is running.
654    /// - **HTTP/3** (QUIC) on `tls_addr` (if the `http3` feature is enabled).
655    /// - **HTTPS** (HTTP/1.1 + HTTP/2 over TLS) on `tls_addr`, which blocks
656    ///   the current task.
657    ///
658    /// # Arguments
659    /// - `tls_addr`: The address to bind for TLS (e.g., `"0.0.0.0:443"`).
660    /// - `cleartext_addr`: Optional plaintext HTTP address for the redirect listener
661    ///   (e.g., `Some("0.0.0.0:80")`). Pass `None` if you manage HTTP elsewhere.
662    /// - `cert_pem`: PEM-encoded certificate chain (leaf + intermediates).
663    /// - `key_pem`: PEM-encoded ECDSA or RSA private key.
664    ///
665    /// # Errors
666    /// Returns an error if address binding, TLS configuration, or certificate parsing fails.
667    #[cfg(feature = "cert-gen")]
668    pub async fn start_all(
669        self,
670        tls_addr: &str,
671        cleartext_addr: Option<&str>,
672        cert_pem: String,
673        key_pem: String,
674    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
675        self.start_all_inner(tls_addr, cleartext_addr, cert_pem, key_pem)
676            .await
677    }
678
679    /// Starts the server with **automatic Let's Encrypt certificate management**.
680    ///
681    /// This is the simplest way to deploy a production HTTPS server with Tachyon.
682    /// It combines [`AcmeManager`] (certificate issuance and renewal) with [`start_all`]
683    /// (multi-protocol serving) into a single call.
684    ///
685    /// # What this does
686    ///
687    /// 1. Creates an [`AcmeManager`] for the given `domains` and `email`.
688    /// 2. Starts the ACME background renewal loop.
689    /// 3. Binds an HTTP listener on `cleartext_addr` that:
690    ///    - Serves ACME HTTP-01 challenge responses (required for cert issuance).
691    ///    - Redirects all other requests to HTTPS with `308 Permanent Redirect`.
692    /// 4. Waits (up to 30s) for the first certificate to be cached or
693    ///    provisioned, then starts the TLS listener regardless of whether
694    ///    that wait timed out.
695    /// 5. Optionally starts HTTP/3 QUIC listener (if the `http3` feature is enabled).
696    ///
697    /// # Arguments
698    /// - `tls_addr`: Address to bind for HTTPS (e.g., `"0.0.0.0:443"`).
699    /// - `cleartext_addr`: Address to bind for HTTP and ACME challenges (e.g., `"0.0.0.0:80"`).
700    ///   **Port 80 must be publicly reachable** for Let's Encrypt HTTP-01 challenges to work.
701    /// - `domains`: Domain names to include in the certificate (must all resolve to this server).
702    /// - `email`: Contact email for Let's Encrypt account registration and expiry notices.
703    /// - `cache_dir`: Directory to store credentials and the certificate on disk.
704    ///   Must be writable. Survives server restarts — this prevents hitting rate limits.
705    /// - `staging`: If `true`, uses the Let's Encrypt **staging** environment.
706    ///   Recommended for testing; staging issues untrusted certs but has much higher rate limits.
707    ///
708    /// # Example
709    ///
710    /// ```rust,no_run
711    /// use tachyon_web::{Router, Server, get};
712    ///
713    /// async fn hello() -> &'static str { "Hello, HTTPS World!" }
714    ///
715    /// #[tokio::main]
716    /// async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
717    ///     #[cfg(feature = "lets-encrypt")]
718    ///     {
719    ///         let app = Router::new().route("/", get(hello));
720    ///
721    ///         Server::new(app)
722    ///             .serve_all_acme(
723    ///                 "0.0.0.0:443",
724    ///                 "0.0.0.0:80",
725    ///                 vec!["example.com".to_string(), "www.example.com".to_string()],
726    ///                 "admin@example.com".to_string(),
727    ///                 "/var/cache/tachyon/certs",
728    ///                 false,  // false = production Let's Encrypt
729    ///             )
730    ///             .await?;
731    ///     }
732    ///     Ok(())
733    /// }
734    /// ```
735    ///
736    /// # Errors
737    /// Returns an error if:
738    /// - The HTTP or HTTPS addresses cannot be bound.
739    /// - The ACME account cannot be created or loaded.
740    /// - Certificate provisioning fails (after exhausting retries).
741    ///
742    /// [`AcmeManager`]: crate::tls::acme::AcmeManager
743    /// [`start_all`]: Server::start_all
744    #[cfg(feature = "lets-encrypt")]
745    pub async fn serve_all_acme(
746        self,
747        tls_addr: &str,
748        cleartext_addr: &str,
749        domains: Vec<String>,
750        email: String,
751        cache_dir: impl Into<std::path::PathBuf>,
752        staging: bool,
753    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
754        use crate::tls::acme::AcmeManager;
755        enforce_fips_compliance()?;
756
757        let acme = AcmeManager::new(cache_dir, domains, email, staging);
758        let resolver = acme.resolver();
759
760        // Start the background renewal loop before attempting to serve.
761        acme.start();
762
763        // Give the renewal loop a bounded window to load a cached cert or
764        // provision a fresh one before the TLS listener starts accepting —
765        // otherwise every connection that lands before the first cert is
766        // ready fails its handshake. If provisioning is still in flight after
767        // the timeout (e.g. a slow ACME order), proceed anyway rather than
768        // hang startup forever; those early connections will fail until the
769        // cert lands, same as today, but the common case (cached or
770        // fast-issued cert) now actually gets served from the start.
771        let wait_start = tokio::time::Instant::now();
772        while !resolver.has_certificate() {
773            if wait_start.elapsed() >= FIRST_CERT_TIMEOUT {
774                tracing::warn!(
775                    "[acme] No certificate ready after {:?}; starting TLS listener anyway — \
776                     connections will fail until provisioning completes",
777                    FIRST_CERT_TIMEOUT
778                );
779                break;
780            }
781            tokio::time::sleep(Duration::from_millis(100)).await;
782        }
783
784        // Build the TLS config backed by the ACME hot-swap resolver, sharing the same
785        // crypto/TLS policy as the onion/i2p listeners (see `Server::tls_policy`).
786        let policy = self.effective_tls_policy();
787        let mut tls_config = rustls::ServerConfig::builder_with_provider(policy.provider())
788            .with_protocol_versions(policy.versions())
789            .map_err(|e| {
790                std::io::Error::new(
791                    std::io::ErrorKind::InvalidData,
792                    format!("TLS version configuration failed: {e}"),
793                )
794            })?
795            .with_no_client_auth()
796            .with_cert_resolver(resolver);
797
798        #[cfg(feature = "http3")]
799        {
800            tls_config.alpn_protocols = alpn_protocols(true);
801        }
802        #[cfg(not(feature = "http3"))]
803        {
804            tls_config.alpn_protocols = alpn_protocols(false);
805        }
806
807        let tls_config = Arc::new(tls_config);
808        let tls_acceptor = TlsAcceptor::from(tls_config.clone());
809
810        #[cfg(feature = "http3")]
811        {
812            let quic_tls = s2n_quic::provider::tls::rustls::Server::from(tls_config);
813            let quic_limits = s2n_quic::provider::limits::Limits::new()
814                .with_data_window(1_048_576)?
815                .with_bidirectional_local_data_window(1_048_576)?
816                .with_bidirectional_remote_data_window(1_048_576)?
817                .with_initial_round_trip_time(Duration::from_millis(100))?
818                .with_max_open_remote_bidirectional_streams(4096)?
819                .with_ack_elicitation_interval(4)?
820                .with_active_connection_migration(false)?
821                .with_max_active_connection_ids(2)?
822                .with_max_handshake_duration(Duration::from_secs(5))?;
823            let quic_server = s2n_quic::Server::builder()
824                .with_tls(quic_tls)?
825                .with_limits(quic_limits)?
826                .with_io(tls_addr)?
827                .start()?;
828
829            let server_h3 = self.clone();
830            drop(tokio::spawn(async move {
831                let _ = server_h3.serve_h3(quic_server).await;
832            }));
833        }
834
835        // Bind the HTTPS listener and serve (blocks the calling task).
836        let addr: std::net::SocketAddr = tls_addr
837            .parse()
838            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
839        let tls_acceptor = Arc::new(tls_acceptor);
840        let redirect_addr: std::net::SocketAddr = cleartext_addr
841            .parse()
842            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
843        let https_port = parse_port(tls_addr, 443);
844        run_worker_pool(
845            self,
846            addr,
847            Some((redirect_addr, https_port)),
848            move |server, listener| {
849                let tls_acceptor = tls_acceptor.clone();
850                async move { server.serve_https(listener, (*tls_acceptor).clone()).await }
851            },
852        )
853        .await?;
854
855        Ok(())
856    }
857
858    /// Internal: shared setup logic for `start_all`.
859    ///
860    /// # Errors
861    /// Returns an error if address binding, TLS configuration, or certificate parsing fails.
862    #[cfg(feature = "cert-gen")]
863    #[allow(clippy::too_many_lines)]
864    async fn start_all_inner(
865        self,
866        tls_addr: &str,
867        cleartext_addr: Option<&str>,
868        cert_pem: String,
869        key_pem: String,
870    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
871        use rustls::pki_types::{CertificateDer, PrivateKeyDer};
872        use rustls_pemfile::{certs, private_key};
873        enforce_fips_compliance()?;
874
875        let mut cert_reader = std::io::BufReader::new(cert_pem.as_bytes());
876        let cert_chain: Vec<CertificateDer<'static>> = certs(&mut cert_reader)
877            .filter_map(std::result::Result::ok)
878            .collect();
879
880        let mut key_reader = std::io::BufReader::new(key_pem.as_bytes());
881        let key_der: PrivateKeyDer<'static> = private_key(&mut key_reader)
882            .map_err(|e| {
883                std::io::Error::new(
884                    std::io::ErrorKind::InvalidData,
885                    format!("Failed to read private key: {e}"),
886                )
887            })?
888            .ok_or_else(|| {
889                std::io::Error::new(
890                    std::io::ErrorKind::InvalidData,
891                    "No private key found in PEM",
892                )
893            })?;
894
895        // Shares the same crypto/TLS policy as the onion/i2p listeners — see
896        // `Server::tls_policy`. Call `.tls_policy(TlsPolicy::hardened().tls13_only())` (or a
897        // fully custom `TlsPolicy`) for stricter version pinning than the default (TLS 1.3
898        // and 1.2 both offered).
899        let policy = self.effective_tls_policy();
900        let mut tls_config = rustls::ServerConfig::builder_with_provider(policy.provider())
901            .with_protocol_versions(policy.versions())
902            .map_err(|e| {
903                std::io::Error::new(
904                    std::io::ErrorKind::InvalidData,
905                    format!("Failed to configure TLS protocol versions: {e}"),
906                )
907            })?
908            .with_no_client_auth()
909            .with_single_cert(cert_chain, key_der)
910            .map_err(|e| {
911                std::io::Error::new(
912                    std::io::ErrorKind::InvalidData,
913                    format!("Invalid certificate or key: {e}"),
914                )
915            })?;
916
917        #[cfg(feature = "http3")]
918        {
919            tls_config.alpn_protocols = alpn_protocols(true);
920        }
921        #[cfg(not(feature = "http3"))]
922        {
923            tls_config.alpn_protocols = alpn_protocols(false);
924        }
925
926        let tls_config = Arc::new(tls_config);
927        let tls_acceptor = TlsAcceptor::from(tls_config.clone());
928        let https_port = parse_port(tls_addr, 443);
929
930        let redirect_info = if let Some(cleartext_addr) = cleartext_addr {
931            let redirect_addr: std::net::SocketAddr = cleartext_addr
932                .parse()
933                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
934            Some((redirect_addr, https_port))
935        } else {
936            None
937        };
938
939        // Start HTTP/3 QUIC Server (if the feature is enabled).
940        #[cfg(feature = "http3")]
941        {
942            let quic_tls = s2n_quic::provider::tls::rustls::Server::from(tls_config);
943            let quic_limits = s2n_quic::provider::limits::Limits::new()
944                .with_data_window(1_048_576)?
945                .with_bidirectional_local_data_window(1_048_576)?
946                .with_bidirectional_remote_data_window(1_048_576)?
947                .with_initial_round_trip_time(Duration::from_millis(100))?
948                .with_max_open_remote_bidirectional_streams(4096)?
949                .with_ack_elicitation_interval(4)?
950                .with_active_connection_migration(false)?
951                .with_max_active_connection_ids(2)?
952                .with_max_handshake_duration(Duration::from_secs(5))?;
953            let quic_server = s2n_quic::Server::builder()
954                .with_tls(quic_tls)?
955                .with_limits(quic_limits)?
956                .with_io(tls_addr)?
957                .start()?;
958
959            let server_h3 = self.clone();
960            drop(tokio::spawn(async move {
961                let _ = server_h3.serve_h3(quic_server).await;
962            }));
963        }
964
965        // Start the HTTPS listener (blocks this task).
966        let addr: std::net::SocketAddr = tls_addr
967            .parse()
968            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
969        let tls_acceptor = Arc::new(tls_acceptor);
970        run_worker_pool(self, addr, redirect_info, move |server, listener| {
971            let tls_acceptor = tls_acceptor.clone();
972            async move { server.serve_https(listener, (*tls_acceptor).clone()).await }
973        })
974        .await?;
975
976        Ok(())
977    }
978}
979
980/// Enforces FIPS compliance on the cryptographic module.
981/// If the `fips` feature is enabled and `aws-lc-rs` is not running in FIPS mode,
982/// returns an error to prevent server startup.
983#[allow(dead_code, clippy::unnecessary_wraps, clippy::missing_const_for_fn)]
984pub(crate) fn enforce_fips_compliance() -> Result<(), std::io::Error> {
985    // `aws_lc_rs` (the crate) is only linked at all when `tls` is enabled (see `dep:aws-lc-rs`
986    // in Cargo.toml) — `tor`/`i2p` no longer pull `tls` in unconditionally, so a
987    // `tor`/`i2p` + `fips` build with `tls` left off has no top-level crypto provider of ours
988    // to check here. (`tachyon-i2p/fips`, forwarded by this crate's own `fips` feature, still
989    // governs `libi2pd`'s *own* separately-linked crypto backend independently of this check.)
990    #[cfg(all(feature = "fips", feature = "tls"))]
991    {
992        if let Err(e) = aws_lc_rs::try_fips_mode() {
993            return Err(std::io::Error::other(format!(
994                "FIPS compliance check failed: {e}. Cryptographic backend is not in FIPS mode!"
995            )));
996        }
997    }
998    Ok(())
999}
1000
1001// ─── Helpers ─────────────────────────────────────────────────────────────────
1002
1003/// Builds the ALPN protocol list for a TLS `ServerConfig`, in preference order,
1004/// matching whichever of `http3`/`http2`/`http1` are actually compiled in — so
1005/// TLS never advertises a protocol the connection-handling code (gated on the
1006/// same features, see `server/http.rs`) has no branch to serve it with.
1007#[cfg(feature = "tls")]
1008pub(crate) fn alpn_protocols(include_h3: bool) -> Vec<Vec<u8>> {
1009    let mut protocols = Vec::with_capacity(3);
1010    if include_h3 {
1011        protocols.push(b"h3".to_vec());
1012    }
1013    #[cfg(feature = "http2")]
1014    protocols.push(b"h2".to_vec());
1015    #[cfg(feature = "http1")]
1016    protocols.push(b"http/1.1".to_vec());
1017    protocols
1018}
1019
1020/// Parses the port number from a bind address string (e.g., `"0.0.0.0:443"`).
1021/// Falls back to `default_port` if parsing fails.
1022#[cfg(any(feature = "cert-gen", feature = "lets-encrypt"))]
1023fn parse_port(addr: &str, default_port: u16) -> u16 {
1024    addr.split(':')
1025        .next_back()
1026        .and_then(|p| p.parse::<u16>().ok())
1027        .unwrap_or(default_port)
1028}
1029
1030pub(crate) fn is_resource_exhaustion(e: &std::io::Error) -> bool {
1031    matches!(e.raw_os_error(), Some(23 | 24 | 10024))
1032}
1033
1034/// Runs a plain HTTP listener that serves two functions:
1035///
1036/// 1. **ACME HTTP-01 challenges**: Any request to `/.well-known/acme-challenge/<token>`
1037///    is answered with the key authorization string from the global challenge store.
1038///    This allows Let's Encrypt to validate domain ownership.
1039///
1040/// 2. **HTTPS redirect**: All other requests receive a `308 Permanent Redirect` to the
1041///    equivalent HTTPS URL. `308` (Permanent Redirect) is preferred over `301` (Moved Permanently)
1042///    because `308` preserves the request method, which is important for `POST` requests.
1043#[cfg(feature = "tls")]
1044pub async fn serve_http_redirect_and_challenges(listener: TcpListener, https_port: u16) {
1045    let builder =
1046        hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
1047
1048    loop {
1049        let (stream, _peer) = match listener.accept().await {
1050            Ok(c) => c,
1051            Err(e) => {
1052                tracing::error!("[http-redirect] Accept error: {e}");
1053                if is_resource_exhaustion(&e) {
1054                    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1055                }
1056                continue;
1057            }
1058        };
1059        let _ = stream.set_nodelay(true);
1060        let io = hyper_util::rt::TokioIo::new(stream);
1061        let builder = builder.clone();
1062
1063        drop(tokio::spawn(async move {
1064            let _ = builder
1065                .serve_connection(
1066                    io,
1067                    service_fn(move |req: Request<hyper::body::Incoming>| {
1068                        #[allow(unused_variables)]
1069                        let path = req.uri().path().to_owned();
1070                        async move {
1071                            // Serve ACME HTTP-01 challenge response.
1072                            #[cfg(feature = "lets-encrypt")]
1073                            if let Some(token) = path.strip_prefix("/.well-known/acme-challenge/")
1074                                && let Some(key_auth) = crate::tls::acme::get_challenge(token)
1075                            {
1076                                let resp = Response::builder()
1077                                    .status(200)
1078                                    .header("content-type", "text/plain")
1079                                    .body(Body::full(bytes::Bytes::from(key_auth)))
1080                                    .unwrap_or_else(|_| Response::new(Body::empty()));
1081                                return Ok::<_, std::convert::Infallible>(resp);
1082                            }
1083
1084                            // 308 Permanent Redirect to HTTPS (preserves method).
1085                            let host = req
1086                                .headers()
1087                                .get("host")
1088                                .and_then(|h| h.to_str().ok())
1089                                .unwrap_or("localhost");
1090                            let host_no_port = host.split(':').next().unwrap_or("localhost");
1091                            let port_suffix = if https_port == 443 {
1092                                String::new()
1093                            } else {
1094                                format!(":{https_port}")
1095                            };
1096                            let path_and_query = req
1097                                .uri()
1098                                .path_and_query()
1099                                .map_or("/", hyper::http::uri::PathAndQuery::as_str);
1100                            let location =
1101                                format!("https://{host_no_port}{port_suffix}{path_and_query}");
1102
1103                            let resp = Response::builder()
1104                                .status(308) // 308 Permanent Redirect preserves the HTTP method.
1105                                .header("location", &location)
1106                                .body(Body::empty())
1107                                .unwrap_or_else(|_| Response::new(Body::empty()));
1108                            Ok::<_, std::convert::Infallible>(resp)
1109                        }
1110                    }),
1111                )
1112                .await;
1113        }));
1114    }
1115}
1116
1117/// Start serving requests from the given `TcpListener` using the provided `Router`.
1118///
1119/// This resolves the listener's local address, automatically compiles the router,
1120/// and runs the high-performance worker pool.
1121///
1122/// # Errors
1123///
1124/// Returns an error if compiling the router fails, or if binding/running the server workers fails.
1125pub async fn serve(
1126    listener: tokio::net::TcpListener,
1127    router: crate::routing::Router<()>,
1128) -> Result<(), std::io::Error> {
1129    let addr = listener.local_addr()?;
1130    // drop the listener so the port is free to bind SO_REUSEPORT sockets in the worker pool
1131    drop(listener);
1132
1133    let server = Server::new(router);
1134    server.start_http_addr(addr).await
1135}
1136
1137/// Configuration for custom rustls server.
1138#[cfg(feature = "tls")]
1139#[derive(Clone)]
1140pub struct RustlsConfig {
1141    pub(crate) server_config: Arc<rustls::ServerConfig>,
1142}
1143
1144#[cfg(feature = "tls")]
1145impl std::fmt::Debug for RustlsConfig {
1146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1147        f.debug_struct("RustlsConfig").finish_non_exhaustive()
1148    }
1149}
1150
1151#[cfg(feature = "tls")]
1152impl RustlsConfig {
1153    /// Create a new `RustlsConfig` from PEM-formatted certificate chain and private key bytes.
1154    ///
1155    /// # Errors
1156    /// Returns an error if the certificates or private key cannot be parsed, or if the config is invalid.
1157    #[allow(clippy::unused_async)]
1158    pub async fn from_pem(cert: Vec<u8>, key: Vec<u8>) -> Result<Self, std::io::Error> {
1159        use rustls::pki_types::{CertificateDer, PrivateKeyDer};
1160        use rustls_pemfile::{certs, private_key};
1161
1162        let mut cert_reader = std::io::BufReader::new(cert.as_slice());
1163        let cert_chain: Vec<CertificateDer<'static>> = certs(&mut cert_reader)
1164            .filter_map(std::result::Result::ok)
1165            .collect();
1166
1167        let mut key_reader = std::io::BufReader::new(key.as_slice());
1168        let key_der: PrivateKeyDer<'static> = private_key(&mut key_reader)
1169            .map_err(|e| {
1170                std::io::Error::new(
1171                    std::io::ErrorKind::InvalidData,
1172                    format!("Failed to read private key: {e}"),
1173                )
1174            })?
1175            .ok_or_else(|| {
1176                std::io::Error::new(std::io::ErrorKind::NotFound, "No private key found in PEM")
1177            })?;
1178
1179        let mut server_config = rustls::ServerConfig::builder()
1180            .with_no_client_auth()
1181            .with_single_cert(cert_chain, key_der)
1182            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
1183
1184        server_config.alpn_protocols = alpn_protocols(false);
1185
1186        Ok(Self {
1187            server_config: Arc::new(server_config),
1188        })
1189    }
1190}
1191
1192/// Create an HTTPS server bound to the given `SocketAddr` using the provided `RustlsConfig`.
1193#[cfg(feature = "tls")]
1194#[must_use]
1195pub const fn bind_rustls(addr: std::net::SocketAddr, config: RustlsConfig) -> HttpsServer {
1196    HttpsServer {
1197        addr,
1198        config,
1199        serve_http3: false,
1200    }
1201}
1202
1203/// An HTTPS server ready to be run.
1204#[cfg(feature = "tls")]
1205pub struct HttpsServer {
1206    addr: std::net::SocketAddr,
1207    config: RustlsConfig,
1208    serve_http3: bool,
1209}
1210
1211#[cfg(feature = "tls")]
1212impl std::fmt::Debug for HttpsServer {
1213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1214        f.debug_struct("HttpsServer")
1215            .field("addr", &self.addr)
1216            .field("serve_http3", &self.serve_http3)
1217            .finish_non_exhaustive()
1218    }
1219}
1220
1221#[cfg(feature = "tls")]
1222impl HttpsServer {
1223    /// Enable or disable HTTP/3 (QUIC) support on the same port.
1224    ///
1225    /// Note: HTTP/3 requires the `http3` feature to be enabled.
1226    #[must_use]
1227    pub const fn serve_http3(mut self, enable: bool) -> Self {
1228        self.serve_http3 = enable;
1229        self
1230    }
1231
1232    /// Run the server with the given router.
1233    ///
1234    /// # Errors
1235    /// Returns an error if compiling the router or running the server fails.
1236    pub async fn serve(self, router: crate::routing::Router<()>) -> Result<(), std::io::Error> {
1237        let server = Server::new(router);
1238        #[cfg_attr(not(feature = "http3"), allow(unused_mut))]
1239        let mut rustls_config = (*self.config.server_config).clone();
1240
1241        #[cfg(feature = "http3")]
1242        if self.serve_http3 {
1243            // Ensure ALPN lists "h3"
1244            if !rustls_config.alpn_protocols.iter().any(|p| p == b"h3") {
1245                rustls_config.alpn_protocols.insert(0, b"h3".to_vec());
1246            }
1247
1248            let tls_config_arc = Arc::new(rustls_config.clone());
1249            let quic_tls = s2n_quic::provider::tls::rustls::Server::from(tls_config_arc);
1250            let quic_limits = s2n_quic::provider::limits::Limits::new()
1251                .with_data_window(1_048_576)
1252                .map_err(std::io::Error::other)?
1253                .with_bidirectional_local_data_window(1_048_576)
1254                .map_err(std::io::Error::other)?
1255                .with_bidirectional_remote_data_window(1_048_576)
1256                .map_err(std::io::Error::other)?
1257                .with_initial_round_trip_time(Duration::from_millis(100))
1258                .map_err(std::io::Error::other)?
1259                .with_max_open_remote_bidirectional_streams(4096)
1260                .map_err(std::io::Error::other)?
1261                .with_ack_elicitation_interval(4)
1262                .map_err(std::io::Error::other)?
1263                .with_active_connection_migration(false)
1264                .map_err(std::io::Error::other)?
1265                .with_max_active_connection_ids(2)
1266                .map_err(std::io::Error::other)?
1267                .with_max_handshake_duration(Duration::from_secs(5))
1268                .map_err(std::io::Error::other)?;
1269
1270            let quic_server = s2n_quic::Server::builder()
1271                .with_tls(quic_tls)
1272                .map_err(std::io::Error::other)?
1273                .with_limits(quic_limits)
1274                .map_err(std::io::Error::other)?
1275                .with_io(self.addr)
1276                .map_err(std::io::Error::other)?
1277                .start()
1278                .map_err(std::io::Error::other)?;
1279
1280            let server_h3 = server.clone();
1281            tokio::spawn(async move {
1282                let _ = server_h3.serve_h3(quic_server).await;
1283            });
1284        }
1285
1286        server
1287            .start_https_with_config_addr(self.addr, rustls_config)
1288            .await
1289    }
1290}
1291
1292#[cfg(test)]
1293mod tests {
1294    use super::*;
1295    use crate::routing::Router;
1296
1297    /// `Server::clone()` is a hand-written impl (not `#[derive(Clone)]`, since the field
1298    /// list is feature-gated) — this proves it actually copies every field rather than
1299    /// silently dropping one when a new field is added.
1300    #[test]
1301    #[allow(clippy::redundant_clone)] // the point of this test is exercising `Clone` itself.
1302    fn clone_preserves_body_size_and_max_connections() {
1303        let server = Server::new(Router::new())
1304            .max_body_size(4096)
1305            .max_connections(7);
1306        let cloned = server.clone();
1307        assert_eq!(cloned.max_body_size, 4096);
1308        assert_eq!(cloned.max_connections, 7);
1309    }
1310
1311    #[cfg(feature = "tls")]
1312    #[test]
1313    #[allow(clippy::redundant_clone)] // the point of this test is exercising `Clone` itself.
1314    fn clone_preserves_tls_policy() {
1315        let server =
1316            Server::new(Router::new()).tls_policy(crate::tls::TlsPolicy::hardened().tls13_only());
1317        assert!(server.tls_policy.is_some());
1318        let cloned = server.clone();
1319        assert!(cloned.tls_policy.is_some());
1320    }
1321
1322    #[test]
1323    fn max_connections_builder_sets_field() {
1324        let server = Server::new(Router::new()).max_connections(42);
1325        assert_eq!(server.max_connections, 42);
1326        // Default is untouched by an unrelated builder call.
1327        assert_eq!(server.max_body_size, 2 * 1024 * 1024);
1328    }
1329
1330    #[test]
1331    fn is_resource_exhaustion_matches_known_codes() {
1332        assert!(is_resource_exhaustion(&std::io::Error::from_raw_os_error(
1333            24
1334        )));
1335        assert!(is_resource_exhaustion(&std::io::Error::from_raw_os_error(
1336            23
1337        )));
1338        assert!(is_resource_exhaustion(&std::io::Error::from_raw_os_error(
1339            10024
1340        )));
1341    }
1342
1343    #[test]
1344    fn is_resource_exhaustion_false_for_unrelated_errors() {
1345        assert!(!is_resource_exhaustion(&std::io::Error::from_raw_os_error(
1346            2
1347        )));
1348        assert!(!is_resource_exhaustion(&std::io::Error::other(
1349            "not an os error"
1350        )));
1351    }
1352
1353    #[cfg(feature = "tls")]
1354    #[test]
1355    fn rustls_config_debug_smoke() {
1356        let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
1357            .expect("generate self-signed cert");
1358        let mut server_config = rustls::ServerConfig::builder()
1359            .with_no_client_auth()
1360            .with_single_cert(vec![cert.cert_der], cert.key_der)
1361            .expect("build server config");
1362        server_config.alpn_protocols = alpn_protocols(false);
1363        let config = RustlsConfig {
1364            server_config: Arc::new(server_config),
1365        };
1366        let dbg = format!("{config:?}");
1367        assert!(dbg.contains("RustlsConfig"));
1368    }
1369
1370    #[cfg(feature = "tls")]
1371    #[tokio::test]
1372    async fn rustls_config_from_pem_rejects_garbage_input() {
1373        let err = RustlsConfig::from_pem(b"not a cert".to_vec(), b"not a key".to_vec())
1374            .await
1375            .expect_err("garbage PEM must not build a config");
1376        assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
1377    }
1378
1379    #[cfg(feature = "tls")]
1380    #[tokio::test]
1381    async fn rustls_config_from_pem_builds_from_a_valid_self_signed_cert() {
1382        let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
1383            .expect("generate self-signed cert");
1384        let config = RustlsConfig::from_pem(cert.cert_pem.into_bytes(), cert.key_pem.into_bytes())
1385            .await
1386            .expect("build config from valid PEM");
1387        assert!(!config.server_config.alpn_protocols.is_empty());
1388    }
1389
1390    #[cfg(feature = "tls")]
1391    #[test]
1392    fn bind_rustls_and_https_server_builders() {
1393        let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
1394            .expect("generate self-signed cert");
1395        let mut server_config = rustls::ServerConfig::builder()
1396            .with_no_client_auth()
1397            .with_single_cert(vec![cert.cert_der], cert.key_der)
1398            .expect("build server config");
1399        server_config.alpn_protocols = alpn_protocols(false);
1400        let config = RustlsConfig {
1401            server_config: Arc::new(server_config),
1402        };
1403        let addr: std::net::SocketAddr = "127.0.0.1:0".parse().expect("parse addr");
1404
1405        let https_server = bind_rustls(addr, config);
1406        assert_eq!(https_server.addr, addr);
1407        assert!(!https_server.serve_http3);
1408        let dbg = format!("{https_server:?}");
1409        assert!(dbg.contains("HttpsServer"));
1410        assert!(dbg.contains("serve_http3: false"));
1411
1412        let https_server = https_server.serve_http3(true);
1413        assert!(https_server.serve_http3);
1414        let dbg = format!("{https_server:?}");
1415        assert!(dbg.contains("serve_http3: true"));
1416    }
1417}