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