Skip to main content

actus_server/
server.rs

1//! [`Server`] — the hyper-based HTTP server that owns the request lifecycle:
2//! routing, body limiting, the middleware chain, CORS, compression, and the
3//! HTTP-correctness stamping (`Allow`, `Vary`), with graceful shutdown.
4
5#[cfg(feature = "compression")]
6use crate::compression::CompressionLayer;
7use crate::cors::CorsLayer;
8use crate::error::ServerError;
9use crate::middleware::{Middleware, MiddlewareChain, Outcome};
10use crate::request::{Request, has_content_coding, has_undecoded_transfer_coding};
11use crate::router::Router;
12#[cfg(feature = "websocket")]
13use crate::websocket;
14use actus_reply::{Finalizer, ProblemDetails, ReplyData, WebError};
15use bytes::Bytes;
16use http::{HeaderMap, StatusCode};
17#[cfg(feature = "websocket")]
18use http::{HeaderValue, header};
19use http_body_util::combinators::BoxBody;
20use hyper::body::{Body, Incoming};
21use hyper::service::service_fn;
22use hyper::{Request as HyperRequest, Response as HyperResponse};
23use std::future::Future;
24use std::io;
25use std::net::SocketAddr;
26use std::pin::Pin;
27use std::sync::Arc;
28use std::sync::atomic::{AtomicU64, Ordering};
29use std::task::{Context, Poll};
30use std::time::Duration;
31use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
32use tokio::net::{TcpListener, TcpStream};
33use tokio::sync::Semaphore;
34use tokio::task::JoinSet;
35use tracing::{Instrument, Level, error, info, span, warn};
36
37type ResponseBody = BoxBody<Bytes, WebError>;
38
39/// One kibibyte (1024 bytes). For readable byte-size limits, e.g.
40/// `#[controller(max_body_bytes = 4 * KIB)]`.
41pub const KIB: usize = 1024;
42/// One mebibyte (1024 × 1024 bytes), e.g. `Server::with_max_body_bytes(2 * MIB)`.
43pub const MIB: usize = 1024 * KIB;
44/// One gibibyte (1024 × 1024 × 1024 bytes).
45pub const GIB: usize = 1024 * MIB;
46
47/// Default cap on the request body Actus will buffer: **2 MiB** — a safe
48/// ceiling for the common case (JSON APIs, forms). Endpoints that accept larger
49/// bodies (uploads) opt in via [`Server::with_max_body_bytes`] or a
50/// per-controller `#[controller(max_body_bytes = …)]`. Matches axum's default.
51pub const DEFAULT_MAX_BODY_BYTES: usize = 2 * MIB;
52
53/// Default grace period for in-flight connections to finish after a
54/// shutdown signal: 30 seconds. Override with [`Server::with_drain_deadline`].
55pub const DEFAULT_DRAIN_DEADLINE: Duration = Duration::from_secs(30);
56
57/// The main Actus server.
58pub struct Server {
59    router: Arc<Router>,
60    middleware_chain: Arc<MiddlewareChain>,
61    finalizer: Arc<Finalizer>,
62    max_body_bytes: usize,
63    cors: Option<Arc<CorsLayer>>,
64    #[cfg(feature = "compression")]
65    compression: Option<Arc<CompressionLayer>>,
66    /// `Some(d)` caps each request's total time (parse → middleware →
67    /// handler → after-chain → finalize) at `d`; an over-budget request is
68    /// aborted and replied with `504 Gateway Timeout`. `None` disables the
69    /// per-request timer (the default).
70    request_timeout: Option<Duration>,
71    /// Grace period for in-flight connections to drain after shutdown.
72    drain_deadline: Duration,
73    /// Cap on concurrent connection tasks. `Some(n)` installs an
74    /// `Arc<Semaphore>` of `n` permits in the accept loop; while at
75    /// capacity, the loop pauses on permit acquisition and new SYNs queue
76    /// in the kernel's accept backlog (`SOMAXCONN`), at which point the
77    /// kernel drops them. `None` is unbounded.
78    max_connections: Option<usize>,
79    /// Cap on the total bytes being buffered across all in-flight body
80    /// reads. `Some(n)` installs a byte-permit semaphore; each
81    /// `collect_body_capped` reserves its per-request cap upfront and
82    /// releases the permits when the body is buffered or rejected. Refuses
83    /// excess requests with `503 Service Unavailable` (via `WebError::Busy`).
84    /// `None` is unbounded.
85    max_inflight_body_bytes: Option<Arc<Semaphore>>,
86    /// `Some(d)` is forwarded to hyper's `http1::Builder::header_read_timeout`
87    /// — bounds how long after starting to read request headers we'll wait
88    /// before dropping the connection. Catches slowloris and clients that
89    /// TCP-connect-and-send-nothing. `None` leaves hyper's default (none).
90    header_read_timeout: Option<Duration>,
91    /// `Some(counter)`: every byte written to a connection is added to
92    /// `counter` ([`Server::with_sent_bytes`]). `None` counts nothing (the
93    /// default).
94    sent_bytes: Option<Arc<AtomicU64>>,
95}
96
97impl Server {
98    /// Create a server for `router` with default settings: no middleware, no
99    /// CORS, the default body cap, and no DoS limits. Configure it with the
100    /// `with_*` builder methods, then call [`run`](Self::run).
101    pub fn new(router: Router) -> Self {
102        Self {
103            router: Arc::new(router),
104            middleware_chain: Arc::new(MiddlewareChain::new()),
105            finalizer: Arc::new(Finalizer::new()),
106            max_body_bytes: DEFAULT_MAX_BODY_BYTES,
107            cors: None,
108            #[cfg(feature = "compression")]
109            compression: None,
110            request_timeout: None,
111            drain_deadline: DEFAULT_DRAIN_DEADLINE,
112            max_connections: None,
113            max_inflight_body_bytes: None,
114            header_read_timeout: None,
115            sent_bytes: None,
116        }
117    }
118
119    /// The router this server serves, shared.
120    ///
121    /// Lets application code keep using the route tree after `Server::new`
122    /// has consumed it — most usefully from a `Middleware` that wants the
123    /// framework's own longest-prefix matcher instead of re-deriving it. A
124    /// declaration-keyed gate is the motivating case:
125    ///
126    /// ```ignore
127    /// let server = Server::new(router);
128    /// let router = server.router(); // Arc<Router>
129    /// let server = server.with_middleware(FloorGate { router });
130    /// // …in the gate's `before`:
131    /// //   if let Some(rm) = self.router.match_controller(&req.path_parts) {
132    /// //       if rm.controller.actus_expects() == Some("credential") && … { … }
133    /// //   }
134    /// ```
135    ///
136    /// The clone is an `Arc` clone; a gate's extra `match_controller` call is
137    /// one map lookup per path segment on top of the one the server already
138    /// does. See the README's "Route families" section for the full pattern.
139    /// A request routed by a host alias needs nothing extra: its `path_parts`
140    /// already hold the aliased path by the time any middleware runs.
141    pub fn router(&self) -> Arc<Router> {
142        Arc::clone(&self.router)
143    }
144
145    /// Adds a middleware to the server's request processing chain.
146    pub fn with_middleware(mut self, middleware: impl Middleware + 'static) -> Self {
147        let mut chain = Arc::try_unwrap(self.middleware_chain).unwrap_or_else(|arc| (*arc).clone());
148        chain.add(middleware);
149        self.middleware_chain = Arc::new(chain);
150        self
151    }
152
153    /// Enables CORS with the given policy. The server then answers preflight
154    /// (`OPTIONS`) requests itself and adds the `Access-Control-*` headers to
155    /// every cross-origin response (including error responses). See
156    /// [`CorsLayer`].
157    pub fn with_cors(mut self, cors: CorsLayer) -> Self {
158        self.cors = Some(Arc::new(cors));
159        self
160    }
161
162    /// Enables response compression (gzip / brotli). For each response Actus
163    /// picks an encoding from the request's `Accept-Encoding` and compresses
164    /// buffered, compressible bodies above the layer's size threshold. See
165    /// [`CompressionLayer`]. *(Requires the `compression` feature.)*
166    #[cfg(feature = "compression")]
167    pub fn with_compression(mut self, layer: CompressionLayer) -> Self {
168        self.compression = Some(Arc::new(layer));
169        self
170    }
171
172    /// Counts every byte the server writes to its connections into `counter`
173    /// — what actually went out: status lines and headers, bodies after
174    /// compression, chunked framing, and the frames of an upgraded
175    /// (WebSocket) connection. Each write is counted as the operating system
176    /// accepts it, so the count is live, and a client that disconnects
177    /// mid-response leaves only what was written before the connection
178    /// failed.
179    ///
180    /// Request bytes are not counted, nor is anything outside the connection:
181    /// TCP/IP overhead, or what a proxy in front of the server adds (TLS) or
182    /// changes.
183    ///
184    /// The counter is the caller's: read it, or swap it to zero, from
185    /// anywhere. Typical use is metering a deployment's egress.
186    pub fn with_sent_bytes(mut self, counter: Arc<AtomicU64>) -> Self {
187        self.sent_bytes = Some(counter);
188        self
189    }
190
191    /// Caps the request body Actus will buffer (default
192    /// [`DEFAULT_MAX_BODY_BYTES`] = 2 MiB). A larger body is rejected with
193    /// `413 Payload Too Large` before it can exhaust memory — the limit
194    /// bounds buffered bytes, so it also covers chunked bodies that lie about
195    /// (or omit) `Content-Length`.
196    ///
197    /// `0` is accepted and means "reject every non-empty body" — typically
198    /// only useful on a strictly-GET surface that should never see a body.
199    pub fn with_max_body_bytes(mut self, max: usize) -> Self {
200        self.max_body_bytes = max;
201        self
202    }
203
204    /// Cap the total time any single request may take — body parse,
205    /// middleware `before`, handler, middleware `after`, and finalization
206    /// combined. An over-budget request is aborted (the handler's future
207    /// is dropped) and the client gets `504 Gateway Timeout`. No timeout
208    /// is set by default.
209    ///
210    /// **Scope.** The timer covers the request/response exchange. A
211    /// WebSocket upgrade succeeds inside the timer (the `101` is the
212    /// response); the post-upgrade conversation runs in its own task and
213    /// is not bound by this timeout.
214    ///
215    /// **Effect of an over-budget request.** When the timer elapses the
216    /// in-flight future is dropped, which cancels whatever the handler
217    /// was awaiting (DB query, channel recv, etc.). The 504 reply is
218    /// one-shot — the after-chain doesn't run on it (by definition,
219    /// some component upstream was unresponsive; running more risks
220    /// hanging again).
221    pub fn with_request_timeout(mut self, d: Duration) -> Self {
222        self.request_timeout = Some(d);
223        self
224    }
225
226    /// Override the grace period for in-flight connections after a
227    /// shutdown signal (default [`DEFAULT_DRAIN_DEADLINE`] = 30 s).
228    /// Anything still running at the deadline is hard-aborted via
229    /// `JoinSet::shutdown`. Use a longer value for surfaces that hold
230    /// long-lived connections (large file downloads, WebSockets);
231    /// a shorter value for fast-iteration dev workflows. `Duration::ZERO`
232    /// aborts every in-flight task immediately.
233    pub fn with_drain_deadline(mut self, d: Duration) -> Self {
234        self.drain_deadline = d;
235        self
236    }
237
238    /// Cap concurrent connection tasks at `n`. While the cap is held, the
239    /// accept loop pauses on permit acquisition; new SYNs queue in the
240    /// kernel's accept backlog and (once that fills, governed by
241    /// `SOMAXCONN`) get dropped at the OS level. No userland reject /
242    /// no-503-per-conn cost — the kernel handles the spillover.
243    ///
244    /// Each connection task holds its permit until it ends, including the
245    /// post-handshake WebSocket conversation. Size accordingly: a
246    /// `with_max_connections(N)` server can hold `N` long-lived WebSockets
247    /// *before* it stops accepting new connections of any kind.
248    ///
249    /// Unbounded by default (no semaphore installed).
250    pub fn with_max_connections(mut self, n: usize) -> Self {
251        self.max_connections = Some(n);
252        self
253    }
254
255    /// Cap the total bytes being buffered across all in-flight body reads
256    /// at `n`. Each request reserves its per-request cap (see
257    /// `with_max_body_bytes`) from this global budget upfront; if the
258    /// budget is exhausted, the request is refused with `503 Service
259    /// Unavailable` (via [`WebError::Busy`]) and a short `Retry-After`.
260    ///
261    /// Together with [`Self::with_max_connections`] this puts a hard
262    /// ceiling on the framework's memory under adversarial load:
263    /// `with_max_connections(C) * with_max_body_bytes(B)` is the *worst*
264    /// case absent this knob; with it, the ceiling is `min(C * B, this
265    /// value)`.
266    ///
267    /// Pre-reserving the per-request cap over-counts (a 1 KB request
268    /// reserves up to its full cap); the alternative — incremental
269    /// per-chunk byte accounting — is more code for the same effective
270    /// ceiling, and a request that has already started buffering can't
271    /// be sensibly aborted partway through anyway.
272    ///
273    /// `n` is clamped to `u32::MAX` internally (Tokio's `Semaphore`
274    /// permit count uses `u32`); for practical deployments this is no
275    /// limit (4 GiB).
276    ///
277    /// Unbounded by default.
278    pub fn with_max_inflight_body_bytes(mut self, n: usize) -> Self {
279        // u32 cap is a tokio Semaphore constraint, not a design choice.
280        let n_capped = n.min(u32::MAX as usize);
281        self.max_inflight_body_bytes = Some(Arc::new(Semaphore::new(n_capped)));
282        self
283    }
284
285    /// Bound how long after starting to read request headers we'll wait
286    /// before dropping the connection. Forwards to hyper's
287    /// `http1::Builder::header_read_timeout`. Catches slowloris (sending
288    /// headers one byte at a time) and clients that TCP-connect-and-send-
289    /// nothing — the most common file-descriptor-exhaustion attack on a
290    /// keep-alive HTTP server.
291    ///
292    /// Note: hyper 1.x doesn't have a separate "idle between requests"
293    /// timeout (after a complete request, an idle keep-alive connection
294    /// stays open until either side closes or the OS-level TCP keep-alive
295    /// fires). If that matters for your deployment, either disable
296    /// keep-alive entirely upstream of Actus or rely on the OS knobs.
297    ///
298    /// No timeout by default (hyper's default).
299    pub fn with_header_read_timeout(mut self, d: Duration) -> Self {
300        self.header_read_timeout = Some(d);
301        self
302    }
303
304    /// Runs the server on `127.0.0.1:port` (loopback only). For a different
305    /// bind address — e.g. `0.0.0.0:port` to accept connections from other
306    /// hosts in a container — use [`Server::run_on`].
307    ///
308    /// Listens for SIGTERM/SIGINT (Unix) or Ctrl-C (cross-platform) and
309    /// shuts down gracefully: stops accepting new connections, signals
310    /// in-flight connections to finish, and waits up to 30 seconds for
311    /// them to drain before returning.
312    pub async fn run(self, port: u16) -> Result<(), ServerError> {
313        self.run_on(SocketAddr::from(([127, 0, 0, 1], port))).await
314    }
315
316    /// Like [`Server::run`] but binds an arbitrary address. Pass
317    /// `0.0.0.0:port` (or `[::]:port`) to accept connections from other hosts.
318    pub async fn run_on(self, addr: SocketAddr) -> Result<(), ServerError> {
319        self.run_with_shutdown_on(addr, default_shutdown_signal())
320            .await
321    }
322
323    /// Like [`Server::run`] but with a custom shutdown trigger (a future that,
324    /// when it resolves, starts the graceful drain). Binds `127.0.0.1:port`;
325    /// see [`Server::run_with_shutdown_on`] for a custom bind address. Useful
326    /// for tests or for embedding the server in a larger supervision tree.
327    pub async fn run_with_shutdown(
328        self,
329        port: u16,
330        shutdown: impl Future<Output = ()> + Send + 'static,
331    ) -> Result<(), ServerError> {
332        self.run_with_shutdown_on(SocketAddr::from(([127, 0, 0, 1], port)), shutdown)
333            .await
334    }
335
336    /// Bind `addr`, then serve exactly as
337    /// [`Server::run_with_shutdown_listener`] — the general form — does.
338    /// [`Server::run`], [`Server::run_on`], and [`Server::run_with_shutdown`]
339    /// are thin wrappers over this; this is a thin bind over the listener
340    /// form.
341    ///
342    /// **Drain bound.** Once `shutdown` resolves the server stops accepting
343    /// and signals every in-flight connection to wind down. The drain
344    /// deadline defaults to [`DEFAULT_DRAIN_DEADLINE`] (30 s); override
345    /// with [`Server::with_drain_deadline`]. Anything still running at
346    /// the deadline is hard-aborted via `JoinSet::shutdown`. In particular,
347    /// long-lived connections (WebSockets, slow downloads, kept-alive idle
348    /// clients) and any connection task that raced the shutdown notification
349    /// and missed it both get aborted at the deadline rather than draining
350    /// gracefully.
351    pub async fn run_with_shutdown_on(
352        self,
353        addr: SocketAddr,
354        shutdown: impl Future<Output = ()> + Send + 'static,
355    ) -> Result<(), ServerError> {
356        let listener = TcpListener::bind(addr).await?;
357        self.run_with_shutdown_listener(listener, shutdown).await
358    }
359
360    /// Like [`Server::run_on`] but serves on a listener the caller already
361    /// bound (or inherited), with the default SIGTERM / SIGINT shutdown
362    /// trigger. See [`Server::run_with_shutdown_listener`] for what a
363    /// pre-bound listener buys and how to adopt an inherited one.
364    pub async fn run_listener(self, listener: TcpListener) -> Result<(), ServerError> {
365        self.run_with_shutdown_listener(listener, default_shutdown_signal())
366            .await
367    }
368
369    /// The most general form: serve on a listener the caller already bound
370    /// (or inherited) until `shutdown` resolves, then drain. Every other
371    /// `run*` method is a wrapper over this one.
372    ///
373    /// **Why hand the server a listener.** Two cases the bind-it-yourself
374    /// forms can't express:
375    ///
376    /// - **Socket activation** — a supervisor (systemd's `LISTEN_FDS`
377    ///   protocol, launchd, …) owns the listening socket and passes it to
378    ///   the process it spawns. Because the socket outlives the process,
379    ///   connections arriving during a restart queue in the kernel's accept
380    ///   backlog instead of being refused, and the next process serves them.
381    /// - **Race-free embedding and tests** — bind `127.0.0.1:0`, keep the
382    ///   listener, and pass it in; no bind-drop-rebind window in which the
383    ///   port can be lost, and requests may connect before the accept loop
384    ///   even starts (the kernel queues them).
385    ///
386    /// An inherited `std::net::TcpListener` must be set non-blocking before
387    /// conversion — tokio requires it, and a supervisor passes the fd
388    /// blocking:
389    ///
390    /// ```no_run
391    /// # async fn doc(server: actus_server::Server, inherited: std::net::TcpListener)
392    /// # -> Result<(), actus_server::ServerError> {
393    /// inherited.set_nonblocking(true)?;
394    /// let listener = tokio::net::TcpListener::from_std(inherited)?;
395    /// server.run_with_shutdown_listener(listener, std::future::pending()).await
396    /// # }
397    /// ```
398    ///
399    /// **Drain bound.** As [`Server::run_with_shutdown_on`]: once `shutdown`
400    /// resolves the server stops accepting (the listener is dropped — under
401    /// socket activation the *socket* stays open in the supervisor, which is
402    /// the point) and in-flight connections get [`Server::with_drain_deadline`]
403    /// (default [`DEFAULT_DRAIN_DEADLINE`] = 30 s) to finish before being
404    /// aborted. A streaming response that never completes (SSE) always rides
405    /// to the deadline.
406    pub async fn run_with_shutdown_listener(
407        self,
408        listener: TcpListener,
409        shutdown: impl Future<Output = ()> + Send + 'static,
410    ) -> Result<(), ServerError> {
411        match listener.local_addr() {
412            Ok(addr) => info!("Server listening on http://{}", addr),
413            // An inherited fd can decline local_addr (exotic socket family);
414            // serving still works, so log what we know and carry on.
415            Err(_) => info!("Server listening on an inherited socket"),
416        }
417
418        let app = Arc::new(self);
419        // Per-connection cancellation: once `Notify::notify_waiters` fires,
420        // every in-flight task wakes up and asks hyper to gracefully close
421        // its connection (finishing the current response, then exiting).
422        let notify = Arc::new(tokio::sync::Notify::new());
423        let mut tasks: JoinSet<()> = JoinSet::new();
424
425        // Optional cap on concurrent connections. When at-capacity the
426        // accept loop pauses on permit acquisition; new SYNs queue in the
427        // kernel accept backlog (SOMAXCONN) and get dropped at the OS
428        // level once that fills. Each spawned connection task moves its
429        // permit in; the permit releases when the task exits.
430        let conn_permits = app.max_connections.map(|n| Arc::new(Semaphore::new(n)));
431
432        tokio::pin!(shutdown);
433
434        loop {
435            tokio::select! {
436                // Accept-branch: acquire a connection permit first (when
437                // a cap is configured), then accept. The outer select
438                // races this against shutdown so a paused-at-capacity
439                // accept loop still notices the shutdown signal.
440                accept_with_permit = async {
441                    let permit = match &conn_permits {
442                        Some(s) => Some(s.clone().acquire_owned().await.expect("semaphore never closed")),
443                        None => None,
444                    };
445                    let result = listener.accept().await;
446                    (result, permit)
447                } => {
448                    let (accept_result, permit) = accept_with_permit;
449                    let (stream, _peer) = match accept_result {
450                        Ok(s) => s,
451                        Err(e) => {
452                            error!("accept error: {}", e);
453                            // permit released when this branch ends — fine
454                            continue;
455                        }
456                    };
457                    // Every byte the connection writes passes through here —
458                    // responses, hyper's own replies, an upgraded connection's
459                    // frames — so this is where `with_sent_bytes` counts.
460                    let io = hyper_util::rt::TokioIo::new(CountingStream {
461                        inner: stream,
462                        sent_bytes: app.sent_bytes.clone(),
463                    });
464                    let app = app.clone();
465                    let notify = notify.clone();
466                    let header_timeout = app.header_read_timeout;
467                    tasks.spawn(async move {
468                        // The permit (if any) lives for the connection's
469                        // lifetime; releasing happens at task drop.
470                        let _permit = permit;
471
472                        let mut builder = hyper::server::conn::http1::Builder::new();
473                        if let Some(d) = header_timeout {
474                            builder.header_read_timeout(d);
475                        }
476                        let conn = builder.serve_connection(
477                            io,
478                            service_fn(move |req| app.clone().handle_request(req)),
479                        );
480                        // With the `websocket` feature, allow `101 Switching
481                        // Protocols` responses to hand off the connection.
482                        #[cfg(feature = "websocket")]
483                        let conn = conn.with_upgrades();
484                        tokio::pin!(conn);
485                        tokio::select! {
486                            res = conn.as_mut() => {
487                                if let Err(err) = res {
488                                    error!("Error serving connection: {}", err);
489                                }
490                            }
491                            _ = notify.notified() => {
492                                conn.as_mut().graceful_shutdown();
493                                if let Err(err) = conn.await {
494                                    error!("Error during graceful shutdown: {}", err);
495                                }
496                            }
497                        }
498                    });
499                }
500                // Reap finished connection tasks so the `JoinSet` doesn't grow
501                // without bound over the server's lifetime — and so a panicked
502                // connection task is logged promptly, not only at shutdown.
503                joined = tasks.join_next(), if !tasks.is_empty() => {
504                    match joined {
505                        Some(Err(e)) if e.is_panic() => error!("Connection task panicked: {}", e),
506                        Some(Err(e)) => error!("Connection task failed: {}", e),
507                        Some(Ok(())) | None => {}
508                    }
509                }
510                _ = &mut shutdown => {
511                    info!("Shutdown signal received; draining in-flight requests");
512                    break;
513                }
514            }
515        }
516
517        // Stop accepting; signal connections to wind down.
518        drop(listener);
519        notify.notify_waiters();
520
521        // Drain. The grace period is configurable via
522        // `Server::with_drain_deadline` (default 30 s).
523        let drain_deadline = tokio::time::sleep(app.drain_deadline);
524        tokio::pin!(drain_deadline);
525        loop {
526            tokio::select! {
527                next = tasks.join_next() => {
528                    match next {
529                        Some(Ok(())) => {}
530                        Some(Err(e)) if e.is_panic() => {
531                            error!("Connection task panicked: {}", e);
532                        }
533                        Some(Err(e)) => {
534                            error!("Connection task failed: {}", e);
535                        }
536                        None => break,
537                    }
538                }
539                _ = &mut drain_deadline => {
540                    warn!("Drain deadline exceeded; aborting {} connection(s)", tasks.len());
541                    tasks.shutdown().await;
542                    break;
543                }
544            }
545        }
546
547        info!("Server shutdown complete");
548        Ok(())
549    }
550
551    /// Stamp the configured CORS response headers onto `response` (no-op when
552    /// CORS isn't enabled, or the request had no allowed `Origin`). Applied to
553    /// *every* outgoing response — success and error alike — so the browser
554    /// can read 4xx/5xx bodies.
555    fn with_cors_headers(
556        &self,
557        request: &Request,
558        mut response: HyperResponse<ResponseBody>,
559    ) -> HyperResponse<ResponseBody> {
560        if let Some(cors) = &self.cors {
561            cors.apply(&request.headers, response.headers_mut(), false);
562        }
563        response
564    }
565
566    /// Fulfil a `ReplyData::Upgrade` from a handler when the request was
567    /// genuinely a WebSocket handshake: send `101 Switching Protocols` and
568    /// spawn the handler on the upgraded connection. (The "handler returned
569    /// Upgrade but the request wasn't a handshake" case is rewritten to a
570    /// 426 reply in [`Self::finalize_reply`] before reaching this method, so
571    /// it can flow through the after-chain like any other error.)
572    ///
573    /// No CORS headers on the `101`: WebSocket handshakes are scoped by
574    /// browser origin checks (the handler inspects `Origin` itself before
575    /// calling `ws::upgrade`), not by the CORS protocol — `Access-Control-*`
576    /// on a `101` is meaningless to the browser.
577    #[cfg(feature = "websocket")]
578    async fn complete_ws_upgrade(
579        &self,
580        handler: Box<dyn std::any::Any + Send>,
581        ws_upgrade: (hyper::upgrade::OnUpgrade, HeaderValue),
582    ) -> HyperResponse<ResponseBody> {
583        // `ReplyData::Upgrade` is only constructible via `ws::upgrade(...)`,
584        // which always boxes an `UpgradeTask`. A failing downcast would mean
585        // a crate-internal invariant is broken; surface as a panic rather
586        // than silently producing a 500.
587        let task = handler
588            .downcast::<websocket::UpgradeTask>()
589            .expect("ReplyData::Upgrade always carries an UpgradeTask");
590        let (on_upgrade, accept) = ws_upgrade;
591        tokio::spawn(websocket::run_upgrade(on_upgrade, *task));
592        let mut resp = self.finalizer.build_response(ReplyData::Empty).await;
593        *resp.status_mut() = StatusCode::SWITCHING_PROTOCOLS;
594        let h = resp.headers_mut();
595        h.insert(header::CONNECTION, HeaderValue::from_static("upgrade"));
596        h.insert(header::UPGRADE, HeaderValue::from_static("websocket"));
597        h.insert(header::SEC_WEBSOCKET_ACCEPT, accept);
598        resp
599    }
600
601    /// Build the error reply for `error` and route it through
602    /// [`finalize_reply`](Self::finalize_reply), so the after-chain,
603    /// compression, and CORS apply to errors exactly as they do to handler
604    /// successes. This is the canonical way to produce a `WebError`
605    /// response anywhere a `Request` exists.
606    async fn finalize_error(
607        &self,
608        error: WebError,
609        request: &Request,
610        #[cfg(feature = "websocket")] ws_upgrade: Option<(hyper::upgrade::OnUpgrade, HeaderValue)>,
611    ) -> HyperResponse<ResponseBody> {
612        let data = self.finalizer.error_to_reply(error);
613        self.finalize_reply(
614            data,
615            request,
616            #[cfg(feature = "websocket")]
617            ws_upgrade,
618        )
619        .await
620    }
621
622    /// The reply refusing a request whose body would reach the handler still
623    /// encoded, or `None`. Actus decodes no request coding, and a handler must
624    /// never receive encoded bytes as though they were the content: a JSON
625    /// route would fail to parse them, and a route storing bytes would store
626    /// the encoding.
627    ///
628    /// - A transfer coding besides a lone `chunked` is one the server does not
629    ///   understand: `501 Not Implemented` (RFC 9112 §6.1).
630    /// - A content coding other than `identity`: `415 Unsupported Media Type`,
631    ///   with an `Accept-Encoding` naming what would have been accepted (RFC
632    ///   9110 §12.5.3). A request with no body is not refused over the
633    ///   header alone.
634    fn coding_refusal(&self, headers: &HeaderMap, body: &Incoming) -> Option<ReplyData> {
635        if has_undecoded_transfer_coding(headers) {
636            return Some(
637                self.finalizer.error_to_reply(WebError::Problem(
638                    ProblemDetails::new(StatusCode::NOT_IMPLEMENTED, "Not Implemented")
639                        .detail("send the request body with no transfer coding but chunked"),
640                )),
641            );
642        }
643        if !body.is_end_stream() && has_content_coding(headers) {
644            let mut data = self.finalizer.error_to_reply(WebError::Problem(
645                ProblemDetails::new(StatusCode::UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type")
646                    .detail("send the request body without a Content-Encoding"),
647            ));
648            data.add_header("Accept-Encoding", "identity");
649            return Some(data);
650        }
651        None
652    }
653
654    /// Run the after-middleware chain, then turn the reply into a `Response`
655    /// via [`dispatch_reply`](Self::dispatch_reply).
656    ///
657    /// **After-chain runs on every reply with a body and a `Request`.** That
658    /// includes handler successes, `Outcome::Respond` short-circuits, *and*
659    /// every error the application produced (404 / 405 / 401 / 400 / a
660    /// handler-returned `Err(WebError)`, etc.). The README's promise that a
661    /// request-id stamper "still fires on a short-circuit" generalizes to
662    /// every reply — that's the contract this method enforces.
663    ///
664    /// **Exceptions** (the after-chain *doesn't* run):
665    /// - **101 Switching Protocols** — a WebSocket-handshake success has no
666    ///   HTTP body to decorate, and the upgrade machinery consumes the
667    ///   connection.
668    /// - **Pre-parse failures** — a request that fails before
669    ///   [`Request::from_hyper`] returns a skeleton (e.g. malformed HTTP
670    ///   from hyper itself) has no `Request` to give the hook. The body-cap
671    ///   413 and truncated-body 400 are *not* exceptions here: `from_hyper`
672    ///   now returns a skeleton `Request` on those, so they do run through
673    ///   the after-chain.
674    /// - **CORS preflight 204** — synthesized before middleware or routing;
675    ///   not an application request (see [`Self::handle_request`]).
676    async fn finalize_reply(
677        &self,
678        #[allow(unused_mut)] mut data: ReplyData,
679        request: &Request,
680        #[cfg(feature = "websocket")] ws_upgrade: Option<(hyper::upgrade::OnUpgrade, HeaderValue)>,
681    ) -> HyperResponse<ResponseBody> {
682        // If the handler returned `ws::upgrade(...)` but the request isn't a
683        // real WebSocket handshake, rewrite to a 426 error reply *here* so
684        // it flows through the same after-chain / compression / CORS path as
685        // any other error. Only the success-handshake path (Upgrade reply +
686        // ws_upgrade present) keeps the after-chain bypass — a 101 has no
687        // HTTP body to decorate.
688        #[cfg(feature = "websocket")]
689        if matches!(data, ReplyData::Upgrade(_)) && ws_upgrade.is_none() {
690            data = self.finalizer.error_to_reply(WebError::Problem(
691                ProblemDetails::new(StatusCode::UPGRADE_REQUIRED, "WebSocket Upgrade Required")
692                    .detail("this endpoint expects a WebSocket handshake"),
693            ));
694        }
695
696        let needs_after_chain = !matches!(data, ReplyData::Upgrade(_));
697        if needs_after_chain
698            && let Err(e) = self
699                .middleware_chain
700                .process_response(request, &mut data)
701                .await
702        {
703            // After-chain itself errored. Build a plain error response
704            // (no further after-chain — recursion prevention) so a buggy
705            // hook can't infinite-loop the request.
706            return self.with_cors_headers(request, self.finalizer.build_error(e).await);
707        }
708        self.dispatch_reply(
709            data,
710            request,
711            #[cfg(feature = "websocket")]
712            ws_upgrade,
713        )
714        .await
715    }
716
717    /// Turn a handler's (or a short-circuiting middleware's) `ReplyData` into a
718    /// fully processed response: WebSocket upgrade if it's an `Upgrade` reply;
719    /// otherwise compress (if enabled), finalize, and stamp CORS / `Vary`.
720    async fn dispatch_reply(
721        &self,
722        #[allow(unused_mut)] mut data: ReplyData,
723        request: &Request,
724        #[cfg(feature = "websocket")] ws_upgrade: Option<(hyper::upgrade::OnUpgrade, HeaderValue)>,
725    ) -> HyperResponse<ResponseBody> {
726        // A handler that returned `ws::upgrade(...)`: complete the handshake
727        // instead of finalizing a body. `finalize_reply` only lets us reach
728        // here for an `Upgrade` reply when the request *was* a real
729        // handshake (otherwise it rewrote the reply to a 426 error), so
730        // `ws_upgrade` is guaranteed `Some` on this branch.
731        #[cfg(feature = "websocket")]
732        if matches!(data, ReplyData::Upgrade(_)) {
733            let ReplyData::Upgrade(handler) = data else {
734                unreachable!()
735            };
736            let ws_upgrade =
737                ws_upgrade.expect("finalize_reply rewrites Upgrade-without-handshake to 426");
738            return self.complete_ws_upgrade(handler, ws_upgrade).await;
739        }
740        // Compression is the last transform — after response middleware,
741        // before the bytes leave. (Only buffered, compressible bodies above
742        // the threshold are touched.)
743        #[cfg(feature = "compression")]
744        if let Some(c) = &self.compression {
745            data = c.compress_reply(
746                data,
747                request
748                    .headers
749                    .get("accept-encoding")
750                    .and_then(|v| v.to_str().ok()),
751            );
752        }
753        let response = self.finalizer.build_response(data).await;
754        let response = self.with_cors_headers(request, response);
755        #[cfg(feature = "compression")]
756        let response = crate::compression::tag_vary_if_encoded(response);
757        response
758    }
759
760    /// Handles an individual incoming `hyper::Request`.
761    ///
762    /// Wraps [`handle_request_inner`](Self::handle_request_inner) in a
763    /// per-request timeout when one is configured (see
764    /// [`Server::with_request_timeout`]); a timed-out request gets a
765    /// one-shot `504 Gateway Timeout` (no after-chain, since by definition
766    /// something upstream was unresponsive).
767    ///
768    /// Every reply with a `Request` flows through
769    /// [`finalize_reply`](Self::finalize_reply) — handler successes,
770    /// `Outcome::Respond` short-circuits, and *every* error (middleware
771    /// `Err`, body parse failure, 404 / 405 from the router, handler-returned
772    /// `Err`, the 415 / 501 for an encoded body, even the 413 / 400 from the
773    /// body-cap path). The after-chain, compression, and CORS apply
774    /// uniformly. CORS preflight is the one short-circuit that bypasses the
775    /// pipeline — it's HTTP-protocol traffic rather than an application
776    /// request.
777    async fn handle_request(
778        self: Arc<Self>,
779        req: HyperRequest<Incoming>,
780    ) -> Result<HyperResponse<ResponseBody>, hyper::Error> {
781        let timeout = self.request_timeout;
782        let app = self.clone();
783        let inner = app.handle_request_inner(req);
784        match timeout {
785            None => inner.await,
786            Some(d) => match tokio::time::timeout(d, inner).await {
787                Ok(r) => r,
788                Err(_) => {
789                    warn!(timeout = ?d, "request exceeded configured timeout");
790                    Ok(self.finalizer.build_error(WebError::Timeout).await)
791                }
792            },
793        }
794    }
795
796    /// The actual request pipeline. Split from
797    /// [`handle_request`](Self::handle_request) so the latter can wrap it
798    /// in a timeout when one is configured.
799    ///
800    /// **Lifecycle order:**
801    ///
802    /// 1. capture WS upgrade (if request looks like a handshake)
803    /// 2. build the `Request` skeleton (no body yet) — and, when the router
804    ///    has host aliases, route an aliased request as the path it names
805    /// 3. CORS preflight short-circuit (uses headers only)
806    /// 4. match controller — 404 short-circuits *without* buffering the
807    ///    body (efficiency win on adversarial bad-path requests); then stamp
808    ///    the matched controller's rate-limit class onto the request, so a
809    ///    `before` middleware (which only gets `&Request`) can read it; then
810    ///    refuse a body that would arrive still encoded (`415` / `501`),
811    ///    also without reading it
812    /// 5. buffer the body, capped per the resolved policy (today: server-
813    ///    wide; soon, per-controller / per-route)
814    /// 6. middleware `before`
815    /// 7. `to_params` (Content-Type-driven body parse)
816    /// 8. dispatch via the already-matched controller
817    /// 9. middleware `after` + finalize
818    ///
819    /// The "route before buffer" order is what lets the body cap depend on
820    /// the matched route — without it, the framework would have to commit
821    /// to a single cap before knowing where the request is headed.
822    async fn handle_request_inner(
823        self: Arc<Self>,
824        #[allow(unused_mut)] mut req: HyperRequest<Incoming>,
825    ) -> Result<HyperResponse<ResponseBody>, hyper::Error> {
826        let request_span = span!(Level::INFO, "request");
827        async move {
828            // 1. Capture the WS upgrade handshake (if any) before
829            //    `from_hyper_parts` consumes the request: the `OnUpgrade`
830            //    future and the derived `Sec-WebSocket-Accept`. (See
831            //    `websocket` module docs for why this happens up front.)
832            #[cfg(feature = "websocket")]
833            let ws_upgrade: Option<(hyper::upgrade::OnUpgrade, HeaderValue)> =
834                if websocket::is_upgrade_request(req.method(), req.headers()) {
835                    websocket::accept_key(req.headers())
836                        .map(|accept| (hyper::upgrade::on(&mut req), accept))
837                } else {
838                    None
839                };
840
841            // 2. Build the skeleton (method / path / query / headers); the
842            //    body stream is held aside for step 5.
843            //
844            //    A host alias is applied here, before anything else sees the
845            //    request: from now on, for the router, every middleware and
846            //    the handler, it *is* the request for the aliased path. With
847            //    no aliases registered this is the only host work there is —
848            //    one check; `Host` is not even read. The host is read before
849            //    the skeleton takes the request, because an absolute-form
850            //    request's authority is in the URI, which the skeleton does
851            //    not keep.
852            let (mut request, body_stream, alias_prefix) = if self.router.has_host_aliases() {
853                let hit = self.router.host_alias_hit(req.uri(), req.headers());
854                let (mut request, body_stream) = Request::from_hyper_parts(req);
855                let alias_prefix = hit.and_then(|hit| hit.apply(&mut request.path_parts));
856                (request, body_stream, alias_prefix)
857            } else {
858                let (request, body_stream) = Request::from_hyper_parts(req);
859                (request, body_stream, None)
860            };
861
862            // 3. CORS preflight: synthesize the 204 ourselves before any
863            //    application-layer work. Preflights are HTTP-protocol
864            //    traffic; neither `before` nor `after` middleware runs on
865            //    them (see `CLAUDE.md` principle 1).
866            if let Some(cors) = &self.cors
867                && CorsLayer::is_preflight(&request.method, &request.headers)
868            {
869                let mut resp = self.finalizer.build_response(ReplyData::Empty).await;
870                cors.apply(&request.headers, resp.headers_mut(), true);
871                return Ok(resp);
872            }
873
874            // 4. Match controller. A path that hits nothing is 404 *before*
875            //    body buffering — a 10 MiB POST to a non-existent URL no
876            //    longer wastes 10 MiB of memory.
877            let route_match = match self.router.match_controller(&request.path_parts) {
878                Some(rm) => rm,
879                None => {
880                    return Ok(self
881                        .finalize_error(
882                            WebError::NotFound,
883                            &request,
884                            #[cfg(feature = "websocket")]
885                            ws_upgrade,
886                        )
887                        .await);
888                }
889            };
890
891            // 4b. Stamp the matched controller's rate-limit class onto the
892            //     request (the skeleton predates routing, so it was `None`).
893            //     This is the one piece of routing context a `before`
894            //     middleware can't otherwise see — it gets `&mut Request`,
895            //     not the matched controller. An application rate-limit
896            //     middleware reads `request.rate_limit_class` and applies its
897            //     own per-class policy; the framework owns the label and the
898            //     `429` response, not the limiter. (Set before the body
899            //     buffer so it survives `collect_body` and is present for the
900            //     whole pipeline, including the after-chain on error replies.)
901            request.rate_limit_class = route_match.controller.actus_rate_limit();
902
903            // 4c. Refuse a body that would reach the handler still encoded
904            //     (`415` / `501`, see `coding_refusal`) — before reading it,
905            //     so the body cap and the inflight budget are never touched.
906            if let Some(data) = self.coding_refusal(&request.headers, &body_stream) {
907                return Ok(self
908                    .finalize_reply(
909                        data,
910                        &request,
911                        #[cfg(feature = "websocket")]
912                        ws_upgrade,
913                    )
914                    .await);
915            }
916
917            // 5. Resolve the effective body cap: the matched controller's
918            //    `#[controller(max_body_bytes = …)]` if it set one, otherwise
919            //    the server-wide `with_max_body_bytes` cap (default 2 MiB). A
920            //    future Phase 2 adds per-route overrides at the top of this
921            //    fall-through.
922            //
923            //    The error path returns the same skeleton so the after-chain
924            //    still has a `Request`.
925            let effective_cap = route_match
926                .controller
927                .actus_max_body_bytes()
928                .unwrap_or(self.max_body_bytes);
929            request = match request
930                .collect_body(
931                    body_stream,
932                    effective_cap,
933                    self.max_inflight_body_bytes.as_ref(),
934                )
935                .await
936            {
937                Ok(r) => r,
938                Err((request, e)) => {
939                    warn!("rejecting request before parse: {}", e);
940                    return Ok(self
941                        .finalize_error(
942                            e,
943                            &request,
944                            #[cfg(feature = "websocket")]
945                            ws_upgrade,
946                        )
947                        .await);
948                }
949            };
950
951            // 6. Middleware `before` chain.
952            let pre_data: Option<ReplyData> =
953                match self.middleware_chain.process_request(&mut request).await {
954                    Ok(Outcome::Continue) => None,
955                    Ok(Outcome::Respond(data)) => Some(data),
956                    Err(e) => {
957                        return Ok(self
958                            .finalize_error(
959                                e,
960                                &request,
961                                #[cfg(feature = "websocket")]
962                                ws_upgrade,
963                            )
964                            .await);
965                    }
966                };
967
968            // A `before` hook short-circuited with a reply — skip routing
969            // and the handler, but still run the after-chain.
970            if let Some(data) = pre_data {
971                return Ok(self
972                    .finalize_reply(
973                        data,
974                        &request,
975                        #[cfg(feature = "websocket")]
976                        ws_upgrade,
977                    )
978                    .await);
979            }
980
981            // 7. Body parse (JSON / form / opaque, per Content-Type).
982            //    Malformed body → 400 through the after-chain.
983            let mut params = match request.to_params() {
984                Ok(p) => p,
985                Err(e) => {
986                    return Ok(self
987                        .finalize_error(
988                            e,
989                            &request,
990                            #[cfg(feature = "websocket")]
991                            ws_upgrade,
992                        )
993                        .await);
994                }
995            };
996            // The router's answer to "did an alias route this?", for the
997            // handler — so it never has to match `Host` a second time.
998            if let Some(prefix) = alias_prefix {
999                params.set_alias_prefix(prefix);
1000            }
1001
1002            // 8. Dispatch via the matched controller. 405 (verb mismatch
1003            //    inside the controller) and handler-returned errors both
1004            //    come back through here.
1005            match route_match
1006                .controller
1007                .actus_dispatch(&route_match.action, params)
1008                .await
1009            {
1010                Ok(data) => Ok(self
1011                    .finalize_reply(
1012                        data,
1013                        &request,
1014                        #[cfg(feature = "websocket")]
1015                        ws_upgrade,
1016                    )
1017                    .await),
1018                Err(e) => Ok(self
1019                    .finalize_error(
1020                        e,
1021                        &request,
1022                        #[cfg(feature = "websocket")]
1023                        ws_upgrade,
1024                    )
1025                    .await),
1026            }
1027        }
1028        .instrument(request_span)
1029        .await
1030    }
1031}
1032
1033/// Default shutdown trigger: resolves on SIGTERM, SIGINT (Unix), or Ctrl-C
1034/// (Windows). This is what [`Server::run`] uses; for tests or embedding,
1035/// see [`Server::run_with_shutdown`].
1036async fn default_shutdown_signal() {
1037    #[cfg(unix)]
1038    {
1039        use tokio::signal::unix::{SignalKind, signal};
1040        let mut sigterm = signal(SignalKind::terminate()).expect("install SIGTERM handler");
1041        let mut sigint = signal(SignalKind::interrupt()).expect("install SIGINT handler");
1042        tokio::select! {
1043            _ = sigterm.recv() => info!("Received SIGTERM"),
1044            _ = sigint.recv() => info!("Received SIGINT"),
1045        }
1046    }
1047    #[cfg(not(unix))]
1048    {
1049        tokio::signal::ctrl_c()
1050            .await
1051            .expect("install Ctrl-C handler");
1052        info!("Received Ctrl-C");
1053    }
1054}
1055
1056/// An accepted connection that adds every byte written to it to the
1057/// [`Server::with_sent_bytes`] counter, when one is configured. Reads pass
1058/// through untouched.
1059struct CountingStream {
1060    inner: TcpStream,
1061    sent_bytes: Option<Arc<AtomicU64>>,
1062}
1063
1064impl CountingStream {
1065    /// Count a write's outcome: the bytes the operating system accepted.
1066    fn count(&self, written: &Poll<io::Result<usize>>) {
1067        if let (Some(counter), Poll::Ready(Ok(n))) = (&self.sent_bytes, written) {
1068            counter.fetch_add(*n as u64, Ordering::Relaxed);
1069        }
1070    }
1071}
1072
1073impl AsyncRead for CountingStream {
1074    fn poll_read(
1075        self: Pin<&mut Self>,
1076        cx: &mut Context<'_>,
1077        buf: &mut ReadBuf<'_>,
1078    ) -> Poll<io::Result<()>> {
1079        Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
1080    }
1081}
1082
1083impl AsyncWrite for CountingStream {
1084    fn poll_write(
1085        self: Pin<&mut Self>,
1086        cx: &mut Context<'_>,
1087        buf: &[u8],
1088    ) -> Poll<io::Result<usize>> {
1089        let this = self.get_mut();
1090        let written = Pin::new(&mut this.inner).poll_write(cx, buf);
1091        this.count(&written);
1092        written
1093    }
1094
1095    fn poll_write_vectored(
1096        self: Pin<&mut Self>,
1097        cx: &mut Context<'_>,
1098        bufs: &[io::IoSlice<'_>],
1099    ) -> Poll<io::Result<usize>> {
1100        let this = self.get_mut();
1101        let written = Pin::new(&mut this.inner).poll_write_vectored(cx, bufs);
1102        this.count(&written);
1103        written
1104    }
1105
1106    // hyper picks its write strategy from this; forwarding it keeps the
1107    // vectored writes a bare `TcpStream` gets.
1108    fn is_write_vectored(&self) -> bool {
1109        self.inner.is_write_vectored()
1110    }
1111
1112    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1113        Pin::new(&mut self.get_mut().inner).poll_flush(cx)
1114    }
1115
1116    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
1117        Pin::new(&mut self.get_mut().inner).poll_shutdown(cx)
1118    }
1119}