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}