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