Skip to main content

churust_core/
engine.rs

1//! The hyper-based HTTP/1.1 serving engine that drives an [`App`] over a real
2//! socket.
3//!
4//! Most applications never call into this module directly — [`App::start`] and
5//! [`App::start_with_shutdown`] bind a listener and delegate to [`serve`]. It is
6//! public so advanced callers can drive serving with a custom address and
7//! shutdown signal.
8
9use crate::app::App;
10use crate::body::Body;
11#[cfg(feature = "tls")]
12use crate::tls::acceptor_from_pem;
13use bytes::Bytes;
14use futures_util::StreamExt;
15use http_body_util::{
16    combinators::UnsyncBoxBody, BodyDataStream, BodyExt, Full, Limited, StreamBody,
17};
18use hyper::body::Frame;
19use hyper::body::Incoming;
20use hyper::service::service_fn;
21use hyper::{Request as HyperRequest, Response as HyperResponse, StatusCode};
22use hyper_util::rt::TokioIo;
23use std::convert::Infallible;
24use std::future::Future;
25use std::net::SocketAddr;
26
27/// Convert a [`Body`] into the boxed hyper body the engine writes to the wire:
28/// `Full` for a buffered payload, `StreamBody` for a lazy stream.
29///
30/// The boxed body is the `!Sync` [`UnsyncBoxBody`] rather than the plan's
31/// `BoxBody`: `Body::Stream` wraps a `Pin<Box<dyn Stream + Send>>` (intentionally
32/// `Send` but not `Sync`), and the resolved `http-body-util` 0.1.3 `BodyExt::boxed`
33/// requires `Self: Send + Sync`. `boxed_unsync` only requires `Send`, and hyper
34/// accepts any `http_body::Body` response body, so behavior is unchanged.
35fn into_boxed_body(body: Body) -> UnsyncBoxBody<Bytes, std::io::Error> {
36    match body {
37        Body::Bytes(bytes) => Full::new(bytes)
38            .map_err(|never| match never {})
39            .boxed_unsync(),
40        Body::Stream(stream) => {
41            let frames = stream.map(|chunk| {
42                chunk
43                    .map(Frame::data)
44                    .map_err(|e| std::io::Error::other(e.to_string()))
45            });
46            StreamBody::new(frames).boxed_unsync()
47        }
48    }
49}
50
51/// Serve `app` on `addr` until `shutdown` resolves (graceful drain).
52///
53/// Uses HTTP/1.1 (`hyper::server::conn::http1::Builder`).  The plan
54/// referenced `auto::Builder` but that type's `serve_connection` returns a
55/// `Connection<'_, …>` that borrows the builder, which cannot be moved into a
56/// `tokio::spawn` closure.  Using `http1::Builder` directly returns an owned
57/// `Connection<I, S>` that is `'static`, compiles correctly, and still
58/// satisfies the HTTP/1.1-only requirement of Plan 1.
59///
60/// Prefer [`App::start`] / [`App::start_with_shutdown`] unless you need to
61/// control the bind address and shutdown future yourself.
62///
63/// [`App::start`]: crate::App::start
64/// [`App::start_with_shutdown`]: crate::App::start_with_shutdown
65///
66/// ```no_run
67/// use churust_core::{Churust, Call, engine};
68/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
69/// let app = Churust::server()
70///     .routing(|r| { r.get("/", |_c: Call| async { "hi" }); })
71///     .build();
72/// let addr = "127.0.0.1:8080".parse().unwrap();
73/// // Serve until Ctrl-C.
74/// engine::serve(app, addr, async { let _ = tokio::signal::ctrl_c().await; }).await?;
75/// # Ok::<(), std::io::Error>(())
76/// # });
77/// ```
78pub async fn serve<F>(app: App, addr: SocketAddr, shutdown: F) -> std::io::Result<()>
79where
80    F: Future<Output = ()> + Send + 'static,
81{
82    let listener = bind_tcp(addr, app.config().backlog)?;
83    let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
84    serve_listener(app, listener, limits, shutdown).await
85}
86
87/// Serve `app` on an already-bound listener until `shutdown` resolves.
88///
89/// Use this when the port must be known before the server starts — a test that
90/// needs the address, a socket passed in by a supervisor, or anything binding
91/// under its own policy.
92///
93/// It also closes a race that [`serve`] cannot: discovering a free port by
94/// binding, dropping, and letting `serve` bind again leaves a window in which
95/// something else can take it. Handing over the listener removes the window.
96///
97/// ```no_run
98/// use churust_core::{Churust, Call, engine};
99/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
100/// let app = Churust::server()
101///     .routing(|r| { r.get("/", |_c: Call| async { "hi" }); })
102///     .build();
103/// let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
104/// let addr = listener.local_addr()?;
105/// println!("listening on {addr}");
106/// engine::serve_on(app, listener, async { let _ = tokio::signal::ctrl_c().await; }).await?;
107/// # Ok::<(), std::io::Error>(())
108/// # });
109/// ```
110pub async fn serve_on<F>(
111    app: App,
112    listener: tokio::net::TcpListener,
113    shutdown: F,
114) -> std::io::Result<()>
115where
116    F: Future<Output = ()> + Send + 'static,
117{
118    let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
119    serve_listener(app, listener, limits, shutdown).await
120}
121
122/// Bind one address, with the backlog and socket options Churust wants.
123///
124/// Separate from serving so several addresses can be bound *before* any accept
125/// loop starts — see [`serve_many`].
126fn bind_tcp(addr: SocketAddr, backlog: u32) -> std::io::Result<tokio::net::TcpListener> {
127    // Bind through a socket so the backlog is ours to choose. tokio's
128    // TcpListener::bind hard-codes 1024, which is fine until it is not.
129    let socket = match addr {
130        std::net::SocketAddr::V4(_) => tokio::net::TcpSocket::new_v4()?,
131        std::net::SocketAddr::V6(_) => tokio::net::TcpSocket::new_v6()?,
132    };
133    // Without this a restart fails while the previous socket sits in
134    // TIME_WAIT — the single most common cause of "address already in use".
135    socket.set_reuseaddr(true)?;
136    socket.bind(addr)?;
137    socket.listen(backlog)
138}
139
140/// Bind a Unix socket, with the backlog Churust was told to use.
141///
142/// Bound through a socket for the same reason `bind_tcp` is: neither std's nor
143/// tokio's `UnixListener::bind` lets the backlog be chosen, so this listener
144/// silently used the platform default — 128 on Linux — however `backlog` was
145/// set. tokio has a `TcpSocket` but no `UnixSocket`, hence `socket2` here and
146/// not there.
147///
148/// The caller is responsible for having established that any node already at
149/// `path` is stale; this only binds.
150#[cfg(unix)]
151fn bind_unix(path: &std::path::Path, backlog: u32) -> std::io::Result<tokio::net::UnixListener> {
152    let socket = socket2::Socket::new(socket2::Domain::UNIX, socket2::Type::STREAM, None)?;
153    socket.bind(&socket2::SockAddr::unix(path)?)?;
154    // Saturating rather than `as`: `backlog` is a `u32` an operator fills in, and
155    // wrapping a large one into a negative `c_int` would ask the kernel for a
156    // backlog of nonsense.
157    socket.listen(backlog.min(i32::MAX as u32) as i32)?;
158    // tokio requires a non-blocking listener; `from_std` documents this as the
159    // caller's job.
160    socket.set_nonblocking(true)?;
161    tokio::net::UnixListener::from_std(std::os::unix::net::UnixListener::from(socket))
162}
163
164/// Serve on an already-bound listener until `shutdown` resolves.
165async fn serve_listener<F>(
166    app: App,
167    listener: tokio::net::TcpListener,
168    limits: std::sync::Arc<AcceptLimits>,
169    shutdown: F,
170) -> std::io::Result<()>
171where
172    F: Future<Output = ()> + Send + 'static,
173{
174    let conn_cfg = ConnSettings::from(app.config());
175    let shutdown_timeout_ms = app.config().shutdown_timeout_ms;
176    let drain = Drain::new();
177    let mut backoff = AcceptBackoff::default();
178    tokio::pin!(shutdown);
179
180    #[cfg(feature = "tls")]
181    let tls_acceptor = match &app.config().tls {
182        Some(t) => Some(acceptor_from_pem(&t.cert, &t.key)?),
183        None => None,
184    };
185
186    loop {
187        // Waiting for a slot and accepting are one cancellable step, raced
188        // against shutdown.
189        //
190        // Both halves can park indefinitely: `accept` when no client comes, and
191        // `connection_slot` when every permit is held. Awaiting either *outside*
192        // the `select!` means a saturated server never reaches the shutdown arm
193        // — SIGTERM would hang until the orchestrator escalates to SIGKILL,
194        // which is a worse failure than the unbounded drain this budget was
195        // added alongside. Both are cancel-safe: a cancelled slot returns its
196        // permit, and a cancelled `accept` has not taken a connection.
197        let acquire = async {
198            // Take the slot before accepting, so excess load waits in the
199            // kernel backlog — the client's problem — rather than as memory and
200            // descriptors in this process.
201            let slot = limits.connection_slot().await;
202            (slot, listener.accept().await)
203        };
204
205        tokio::select! {
206            // Shutdown wins a tie: with a signal pending there is no reason to
207            // begin serving one more connection.
208            biased;
209            _ = &mut shutdown => break,
210            (slot, accepted) = acquire => {
211                let (stream, peer) = match accepted {
212                    Ok(s) => {
213                        backoff.reset();
214                        s
215                    }
216                    Err(e) => {
217                        // A persistent error — EMFILE when the descriptor table
218                        // is full, which is exactly the state a flood drives
219                        // toward — would otherwise spin this loop at full tilt
220                        // and starve the very tasks that would free a slot.
221                        let pause = backoff.next_pause();
222                        tracing::warn!(error = %e, pause_ms = pause.as_millis() as u64, "accept failed");
223                        tokio::time::sleep(pause).await;
224                        continue;
225                    }
226                };
227                #[cfg(feature = "tls")]
228                {
229                    if let Some(acceptor) = tls_acceptor.clone() {
230                        let app = app.clone();
231                        let handshakes = limits.handshakes.clone();
232                        let handshake_timeout = limits.tls_handshake_timeout;
233                        // The token is taken *before* spawning, so a connection
234                        // that is still shaking hands when shutdown fires is
235                        // already counted — otherwise the drain could finish
236                        // while a handshake was in flight. The handshake itself
237                        // runs on the spawned task so a slow one does not block
238                        // the accept loop.
239                        let token = drain.token();
240                        let conn_builder_fut = async move {
241                            // Queueing for the handshake budget and performing
242                            // the handshake are one timed step.
243                            //
244                            // Handshakes get their own, much smaller budget: the
245                            // work is asymmetric, cheap to ask for and expensive
246                            // to answer, so the connection cap alone is too
247                            // loose a bound. But the deadline has to cover the
248                            // wait for that budget as well as the work it
249                            // guards, because the thing being bounded is how
250                            // long a peer that has not yet proved it can speak
251                            // TLS may hold a *connection* permit — and the
252                            // connection permit is taken before this task even
253                            // starts.
254                            //
255                            // Timing only the handshake turned the budget into
256                            // a rate limiter on failure: with the default 256
257                            // permits and a 10s deadline, stalled ClientHellos
258                            // expire 256 per 10s while the rest wait on
259                            // `acquire_owned` with no deadline at all. Filling
260                            // the connection budget that way costs an attacker
261                            // one TCP connect each and blocks the accept loop
262                            // for as long as it takes to drain — minutes, at
263                            // the default caps, rather than the advertised ten
264                            // seconds.
265                            //
266                            // The cost of covering the queue is that under
267                            // genuine handshake overload a legitimate client
268                            // can be dropped while still queued. That is load
269                            // shedding, and it is the behaviour the knob
270                            // promises: no peer holds a connection permit for
271                            // longer than this.
272                            let queue_and_shake = async {
273                                let permit = match &handshakes {
274                                    Some(sem) => sem.clone().acquire_owned().await.ok(),
275                                    None => None,
276                                };
277                                // Held alongside the result so it outlives the
278                                // handshake and is released deliberately below,
279                                // rather than at the end of this block.
280                                (acceptor.accept(stream).await, permit)
281                            };
282                            let (accepted, handshake_permit) = match handshake_timeout {
283                                Some(limit) => {
284                                    match tokio::time::timeout(limit, queue_and_shake).await {
285                                        Ok(pair) => pair,
286                                        Err(_) => {
287                                            // Dropping the future here cancels
288                                            // the acquire as well as the
289                                            // handshake, so a peer that timed
290                                            // out while queued returns nothing
291                                            // and holds nothing.
292                                            //
293                                            // Before the handshake completes
294                                            // there is no HTTP layer, so
295                                            // `header_read_timeout` cannot
296                                            // cover this case.
297                                            tracing::debug!(%peer, "TLS handshake timed out");
298                                            return;
299                                        }
300                                    }
301                                }
302                                None => queue_and_shake.await,
303                            };
304                            match accepted {
305                                Ok(tls_stream) => {
306                                    // The handshake budget is for handshakes; the
307                                    // connection budget takes over from here.
308                                    drop(handshake_permit);
309                                    serve_stream(app, tls_stream, conn_cfg, peer, token, slot).await;
310                                }
311                                // Certificate, protocol-version and SNI failures
312                                // all land here. Logged at debug because an
313                                // internet-facing port sees scanner noise
314                                // constantly, and a warning per probe is its own
315                                // denial of service on the log pipeline.
316                                Err(e) => tracing::debug!(%peer, error = %e, "TLS handshake failed"),
317                            }
318                        };
319                        tokio::spawn(conn_builder_fut);
320                        continue;
321                    }
322                }
323                serve_stream(app.clone(), stream, conn_cfg, peer, drain.token(), slot).await;
324            }
325        }
326    }
327
328    drain.wait(shutdown_timeout_ms).await;
329    Ok(())
330}
331
332/// Connection tracking for graceful shutdown: signal every live connection to
333/// wind down, then wait until the last one has finished.
334///
335/// hyper-util's `GracefulShutdown` cannot do this for us. `Watcher::watch`
336/// requires the sealed `GracefulConnection` trait, which hyper-util 0.1.x
337/// implements for `auto::Connection` but *not* for the upgradeable connection
338/// that WebSockets need — and the trait being sealed means we cannot supply
339/// the impl ourselves. So this reproduces the same two-part contract over the
340/// connection type we actually serve: `UpgradeableConnection` exposes
341/// `graceful_shutdown`, which is the only piece that was ever missing.
342struct Drain {
343    /// Fires once. Every connection task watches it and stops accepting new
344    /// requests on its connection when it flips.
345    signal: tokio::sync::watch::Sender<bool>,
346    /// Handed out by [`Drain::token`] and held for the lifetime of each
347    /// connection task. `wait` finishes when the last clone is dropped, which
348    /// is exactly when the last connection has finished.
349    guard: Option<tokio::sync::mpsc::Sender<()>>,
350    finished: tokio::sync::mpsc::Receiver<()>,
351}
352
353/// A connection's share of the drain: watch one end, hold the other.
354struct DrainToken {
355    signal: tokio::sync::watch::Receiver<bool>,
356    /// Never read. Dropping it is the signal that this connection is done.
357    _guard: tokio::sync::mpsc::Sender<()>,
358}
359
360impl Drain {
361    fn new() -> Self {
362        let (signal, _) = tokio::sync::watch::channel(false);
363        let (guard, finished) = tokio::sync::mpsc::channel(1);
364        Self {
365            signal,
366            guard: Some(guard),
367            finished,
368        }
369    }
370
371    fn token(&self) -> DrainToken {
372        DrainToken {
373            signal: self.signal.subscribe(),
374            // `guard` is Some for as long as the accept loop runs; `wait`
375            // takes it, so this cannot be called afterwards.
376            _guard: self.guard.clone().expect("token after wait"),
377        }
378    }
379
380    /// Tell every connection to wind down, then wait — for at most
381    /// `timeout_ms`, or indefinitely if that is zero.
382    ///
383    /// Bounding this is the point: one slow request must not delay exit
384    /// forever, because under an orchestrator that means being killed rather
385    /// than shutting down cleanly.
386    async fn wait(mut self, timeout_ms: u64) {
387        let _ = self.signal.send(true);
388        // Drop our own guard, or the count never reaches zero.
389        self.guard.take();
390
391        // `recv` resolves with `None` once every clone has been dropped.
392        let drained = async move { while self.finished.recv().await.is_some() {} };
393
394        if timeout_ms == 0 {
395            drained.await;
396        } else {
397            let grace = std::time::Duration::from_millis(timeout_ms);
398            if tokio::time::timeout(grace, drained).await.is_err() {
399                tracing_drain_timeout(timeout_ms);
400            }
401        }
402    }
403}
404
405/// How long an idle connection is given to put its GOAWAY (or HTTP/1 close) on
406/// the wire during shutdown before it is dropped.
407///
408/// Long enough for a frame to be written and flushed; short enough that a
409/// rolling restart is not held up by peers that have nothing to say. Not
410/// configurable: the tunable that matters is the grace period, and this is
411/// deliberately far below any sane value of it.
412const GOAWAY_LINGER: std::time::Duration = std::time::Duration::from_millis(250);
413
414/// The `Content-Type` of the refusals this module composes itself.
415///
416/// Spelled the same way [`Response::text`](crate::Response::text) spells it, so
417/// a `413` the engine wrote and a `413` a handler returned are indistinguishable
418/// to a client parsing the header.
419const TEXT_PLAIN: &str = "text/plain; charset=utf-8";
420
421/// A connection's share of the connection budget, held for as long as the
422/// connection is served. `None` when the budget is unlimited.
423type ConnSlot = Option<tokio::sync::OwnedSemaphorePermit>;
424
425/// A connection's share of *both* budgets, in a form that can outlive the
426/// connection future.
427///
428/// hyper resolves an upgraded connection the moment it dispatches the `101`,
429/// while the socket itself lives on in a detached task. Without handing that
430/// task a share, a live WebSocket held no permit and no drain token at all, so
431/// `max_connections` bounded nothing for WebSocket traffic and the drain could
432/// not see them. Cloning is what lets both the connection loop and the upgraded
433/// task hold it; the budget is returned when the last clone drops.
434#[derive(Clone)]
435pub(crate) struct ConnGuard(#[allow(dead_code)] std::sync::Arc<ConnGuardInner>);
436
437pub(crate) struct ConnGuardInner {
438    _slot: ConnSlot,
439    /// Underscored because only the `ws` build reads it: for an HTTP connection
440    /// the token is held, not watched — `serve_stream` watches the signal
441    /// directly — and merely holding it is what makes the drain wait.
442    _token: DrainToken,
443}
444
445impl ConnGuard {
446    fn new(slot: ConnSlot, token: DrainToken) -> Self {
447        Self(std::sync::Arc::new(ConnGuardInner {
448            _slot: slot,
449            _token: token,
450        }))
451    }
452
453    /// Resolves when the server has been asked to shut down.
454    ///
455    /// Holding a drain token makes the drain *wait* for this connection; it does
456    /// not tell the connection to wind down. For an HTTP connection the loop in
457    /// `serve_stream` watches the signal and does that. An upgraded WebSocket
458    /// leaves that loop the moment the `101` is dispatched — hyper resolves the
459    /// connection there and the socket lives on in a detached task — so the task
460    /// has to watch the signal itself, and until it could, every shutdown waited
461    /// out the full grace period for any live WebSocket and never returned at all
462    /// at `shutdown_timeout_ms = 0`.
463    #[cfg(feature = "ws")]
464    pub(crate) async fn draining(&self) {
465        let mut signal = self.0._token.signal.clone();
466        // `wait_for` rather than `changed`, so a signal that fired before this
467        // was called is still seen — the same reason `serve_stream` uses it.
468        let _ = signal.wait_for(|fired| *fired).await;
469    }
470}
471
472/// How many connections, and how many TLS handshakes, may be in progress.
473///
474/// The listen backlog bounds what the kernel queues before the accept loop
475/// reaches it. These bound what the process takes on. Without them the only
476/// limits are what the OS will hand out, and the failure mode is the process
477/// dying rather than new connections waiting their turn.
478struct AcceptLimits {
479    connections: Option<std::sync::Arc<tokio::sync::Semaphore>>,
480    #[cfg(feature = "tls")]
481    handshakes: Option<std::sync::Arc<tokio::sync::Semaphore>>,
482    #[cfg(feature = "tls")]
483    tls_handshake_timeout: Option<std::time::Duration>,
484}
485
486impl AcceptLimits {
487    fn from(cfg: &crate::app::ServerConfig) -> Self {
488        // `0` means unlimited for every one of these, so a caller who does not
489        // want a bound is not forced to invent a large number.
490        let sem = |n: usize| (n > 0).then(|| std::sync::Arc::new(tokio::sync::Semaphore::new(n)));
491        Self {
492            connections: sem(cfg.max_connections),
493            #[cfg(feature = "tls")]
494            handshakes: sem(cfg.max_tls_handshakes),
495            #[cfg(feature = "tls")]
496            tls_handshake_timeout: (cfg.tls_handshake_timeout_ms > 0)
497                .then(|| std::time::Duration::from_millis(cfg.tls_handshake_timeout_ms)),
498        }
499    }
500
501    /// Wait until a connection may be served. Acquired before `accept`, so
502    /// excess load waits in the kernel backlog rather than in this process.
503    async fn connection_slot(&self) -> ConnSlot {
504        match &self.connections {
505            // `acquire_owned` only fails on a closed semaphore, and nothing
506            // closes these.
507            Some(sem) => sem.clone().acquire_owned().await.ok(),
508            None => None,
509        }
510    }
511}
512
513/// Capped exponential backoff for a failing `accept`.
514///
515/// Capped because an uncapped one turns a transient blip into minutes of
516/// unavailability; reset on success because the next failure is a new problem,
517/// not a continuation of the last one.
518#[derive(Default)]
519struct AcceptBackoff {
520    /// Zero means "no failure yet"; the next pause starts from `FIRST_MS`.
521    current_ms: u64,
522}
523
524impl AcceptBackoff {
525    const FIRST_MS: u64 = 5;
526    const MAX_MS: u64 = 1_000;
527
528    fn reset(&mut self) {
529        self.current_ms = 0;
530    }
531
532    fn next_pause(&mut self) -> std::time::Duration {
533        self.current_ms = match self.current_ms {
534            0 => Self::FIRST_MS,
535            n => (n * 2).min(Self::MAX_MS),
536        };
537        std::time::Duration::from_millis(self.current_ms)
538    }
539}
540
541/// When a connection last did anything, and whether it is doing something now.
542///
543/// hyper's HTTP/1 `keep_alive` knob is a `bool` — reuse the connection or do
544/// not — with no idle bound. Without one, an accepted connection that goes
545/// quiet is held until the client goes away, which costs a file descriptor and
546/// a task each and is the cheapest way to exhaust a server. This is the state
547/// the idle watchdog reads.
548struct ConnActivity {
549    /// Requests currently being served. Idle means zero: a handler slower than
550    /// the keep-alive period is busy, and closing its connection would be a
551    /// self-inflicted truncation.
552    in_flight: std::sync::atomic::AtomicUsize,
553    /// Milliseconds since `origin` at the end of the last request.
554    last_ms: std::sync::atomic::AtomicU64,
555    /// Whether any request has finished on this connection.
556    ///
557    /// Separate from `last_ms` rather than derived from it, because `last_ms` is
558    /// a millisecond count from `origin` and a request served in under a
559    /// millisecond stores `0` — indistinguishable from "nothing has run yet".
560    /// The `keep_alive_ms == 0` close needs exactly that distinction, and it is
561    /// the fast request that would get it wrong.
562    served: std::sync::atomic::AtomicBool,
563    /// Fixed reference point, so activity is a cheap integer rather than a
564    /// mutex around an `Instant`.
565    origin: tokio::time::Instant,
566    /// Signalled when a request finishes, for the `keep_alive_ms == 0` close.
567    ///
568    /// `notify_one` rather than `notify_waiters`, because the two are not
569    /// equivalent under a `select!`: the loop re-creates its `notified()` future
570    /// on every iteration, so a wake delivered while it was polling another
571    /// branch would be lost. `notify_one` stores a permit instead, and the next
572    /// `notified()` consumes it — the completion cannot be missed whatever the
573    /// loop happened to be doing.
574    request_finished: tokio::sync::Notify,
575}
576
577/// Holds a connection's "a request is in flight" count for as long as it lives.
578///
579/// Dropping it records the activity timestamp and decrements. Being a guard
580/// rather than a pair of calls is the point: the future it lives in can be
581/// cancelled, and a leaked increment silently exempts the connection from every
582/// idle and shutdown bound there is.
583struct InFlight(std::sync::Arc<ConnActivity>);
584
585impl InFlight {
586    fn new(activity: std::sync::Arc<ConnActivity>) -> Self {
587        activity
588            .in_flight
589            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
590        Self(activity)
591    }
592}
593
594impl Drop for InFlight {
595    fn drop(&mut self) {
596        self.0.last_ms.store(
597            self.0.origin.elapsed().as_millis() as u64,
598            std::sync::atomic::Ordering::Relaxed,
599        );
600        self.0
601            .served
602            .store(true, std::sync::atomic::Ordering::Relaxed);
603        self.0
604            .in_flight
605            .fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
606        // After the decrement, so a loop woken by this observes the count it is
607        // about to act on rather than the one that was still standing.
608        self.0.request_finished.notify_one();
609    }
610}
611
612/// A response body that holds an [`InFlight`] guard until it is finished.
613///
614/// A transparent wrapper, not a re-stream: `size_hint` and `is_end_stream`
615/// delegate to the inner body so a buffered response keeps its exact length and
616/// hyper still frames it with `Content-Length`. Converting everything to a
617/// stream here would have made every response chunked.
618struct GuardedBody {
619    inner: UnsyncBoxBody<Bytes, std::io::Error>,
620    /// Dropped with the body — after the last frame, or when the client goes
621    /// away mid-transfer.
622    _guard: InFlight,
623}
624
625impl hyper::body::Body for GuardedBody {
626    type Data = Bytes;
627    type Error = std::io::Error;
628
629    fn poll_frame(
630        mut self: std::pin::Pin<&mut Self>,
631        cx: &mut std::task::Context<'_>,
632    ) -> std::task::Poll<Option<std::result::Result<Frame<Bytes>, std::io::Error>>> {
633        std::pin::Pin::new(&mut self.inner).poll_frame(cx)
634    }
635
636    fn size_hint(&self) -> hyper::body::SizeHint {
637        self.inner.size_hint()
638    }
639
640    fn is_end_stream(&self) -> bool {
641        self.inner.is_end_stream()
642    }
643}
644
645/// Keep `guard` alive until the body has been fully written.
646fn attach_guard(
647    body: UnsyncBoxBody<Bytes, std::io::Error>,
648    guard: InFlight,
649) -> UnsyncBoxBody<Bytes, std::io::Error> {
650    GuardedBody {
651        inner: body,
652        _guard: guard,
653    }
654    .boxed_unsync()
655}
656
657impl ConnActivity {
658    fn new() -> Self {
659        Self {
660            in_flight: std::sync::atomic::AtomicUsize::new(0),
661            last_ms: std::sync::atomic::AtomicU64::new(0),
662            served: std::sync::atomic::AtomicBool::new(false),
663            origin: tokio::time::Instant::now(),
664            request_finished: tokio::sync::Notify::new(),
665        }
666    }
667
668    /// Whether a request is being served right now.
669    fn busy(&self) -> bool {
670        self.in_flight.load(std::sync::atomic::Ordering::Relaxed) > 0
671    }
672
673    /// Whether any request on this connection has finished.
674    ///
675    /// Set when an [`InFlight`] guard drops, which is after the response body
676    /// has been written.
677    ///
678    /// Used by the `keep_alive_ms == 0` branch of the connection loop, which must
679    /// close *after* a response and not before the first one. Deliberately not
680    /// `last_ms > 0`: that is a millisecond count from the connection's own
681    /// start, so a request served in under a millisecond records `0` and would
682    /// read as "nothing has run yet" — leaving the fast connections, which is to
683    /// say most of them, never closed.
684    fn has_served(&self) -> bool {
685        self.served.load(std::sync::atomic::Ordering::Relaxed)
686    }
687
688    /// Resolves when a request has finished since the last time it was awaited.
689    async fn request_finished(&self) {
690        self.request_finished.notified().await;
691    }
692
693    /// `None` if the connection has been idle for at least `keep_alive_ms`,
694    /// otherwise how much longer to wait before asking again.
695    fn idle_for(&self, keep_alive_ms: u64) -> Option<std::time::Duration> {
696        if self.in_flight.load(std::sync::atomic::Ordering::Relaxed) > 0 {
697            // Busy. Check again a full period from now rather than computing a
698            // deadline against a request that has not finished.
699            return Some(std::time::Duration::from_millis(keep_alive_ms));
700        }
701        let last = self.last_ms.load(std::sync::atomic::Ordering::Relaxed);
702        let idle_ms = (self.origin.elapsed().as_millis() as u64).saturating_sub(last);
703        match keep_alive_ms.checked_sub(idle_ms) {
704            Some(0) | None => None,
705            Some(remaining) => Some(std::time::Duration::from_millis(remaining)),
706        }
707    }
708}
709
710/// The 24 bytes an HTTP/2 client sends before anything else (RFC 9113 §3.4).
711const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
712
713/// Whether `auto::Builder` has finished deciding which protocol this is.
714///
715/// The two header deadlines the engine configures — `http1().header_read_timeout`
716/// and the h2 keep-alive ping — belong to a `Connection` that hyper-util only
717/// builds *after* it has sniffed the preface, and the sniffing future itself
718/// (`ReadVersion`) holds no timer: it is a bare read of up to 24 bytes. A peer
719/// that connects and sends nothing, or that sends 23 of the 24 preface bytes,
720/// parks there holding a connection permit and a drain token with no deadline
721/// on it at all.
722///
723/// Reading this flag is what lets the connection loop bound that phase and stop
724/// bounding it the moment a protocol exists — which matters, because a live
725/// HTTP/2 client is allowed to sit idle far longer than the header deadline
726/// once it has handshaken.
727#[derive(Default)]
728struct Negotiated(std::sync::atomic::AtomicBool);
729
730impl Negotiated {
731    fn get(&self) -> bool {
732        self.0.load(std::sync::atomic::Ordering::Relaxed)
733    }
734
735    fn set(&self) {
736        self.0.store(true, std::sync::atomic::Ordering::Relaxed);
737    }
738}
739
740/// Wraps the socket to watch the bytes `ReadVersion` reads, and nothing else.
741///
742/// The condition below mirrors hyper-util's: it stops sniffing on the first
743/// read that diverges from the preface, on end of file, and once all 24 bytes
744/// have matched. Mirroring is safe to do here because the preface is a protocol
745/// constant rather than a hyper internal — hyper-util cannot decide differently
746/// without HTTP/2 itself changing.
747///
748/// Once `flag` is set the wrapper is a pure delegate; the comparison costs at
749/// most 24 byte tests per connection.
750struct Sniffing<S> {
751    inner: S,
752    flag: std::sync::Arc<Negotiated>,
753    /// Preface bytes matched so far, across however many reads they arrived in.
754    matched: usize,
755}
756
757impl<S> Sniffing<S> {
758    fn new(inner: S, flag: std::sync::Arc<Negotiated>) -> Self {
759        Self {
760            inner,
761            flag,
762            matched: 0,
763        }
764    }
765
766    /// Feed the bytes a single read produced.
767    fn observe(&mut self, fresh: &[u8]) {
768        // End of file. hyper-util stops sniffing here too, and the connection
769        // is about to end on its own.
770        if fresh.is_empty() {
771            self.flag.set();
772            return;
773        }
774        for &byte in fresh {
775            if byte != H2_PREFACE[self.matched] {
776                // Not HTTP/2: hyper-util builds an HTTP/1 connection, whose own
777                // header deadline takes over from here.
778                self.flag.set();
779                return;
780            }
781            self.matched += 1;
782            if self.matched == H2_PREFACE.len() {
783                self.flag.set();
784                return;
785            }
786        }
787    }
788}
789
790impl<S: tokio::io::AsyncRead + Unpin> tokio::io::AsyncRead for Sniffing<S> {
791    fn poll_read(
792        self: std::pin::Pin<&mut Self>,
793        cx: &mut std::task::Context<'_>,
794        buf: &mut tokio::io::ReadBuf<'_>,
795    ) -> std::task::Poll<std::io::Result<()>> {
796        let this = self.get_mut();
797        let before = buf.filled().len();
798        let polled = std::pin::Pin::new(&mut this.inner).poll_read(cx, buf);
799        if matches!(polled, std::task::Poll::Ready(Ok(()))) && !this.flag.get() {
800            // Copied out because `observe` takes `&mut self` while `buf` is
801            // still borrowed from the read above.
802            let fresh: Vec<u8> = buf.filled()[before..].to_vec();
803            this.observe(&fresh);
804        }
805        polled
806    }
807}
808
809impl<S: tokio::io::AsyncWrite + Unpin> tokio::io::AsyncWrite for Sniffing<S> {
810    fn poll_write(
811        mut self: std::pin::Pin<&mut Self>,
812        cx: &mut std::task::Context<'_>,
813        buf: &[u8],
814    ) -> std::task::Poll<std::io::Result<usize>> {
815        std::pin::Pin::new(&mut self.inner).poll_write(cx, buf)
816    }
817
818    fn poll_flush(
819        mut self: std::pin::Pin<&mut Self>,
820        cx: &mut std::task::Context<'_>,
821    ) -> std::task::Poll<std::io::Result<()>> {
822        std::pin::Pin::new(&mut self.inner).poll_flush(cx)
823    }
824
825    fn poll_shutdown(
826        mut self: std::pin::Pin<&mut Self>,
827        cx: &mut std::task::Context<'_>,
828    ) -> std::task::Poll<std::io::Result<()>> {
829        std::pin::Pin::new(&mut self.inner).poll_shutdown(cx)
830    }
831
832    // Delegated rather than left to the default: hyper writes a response head
833    // and body as separate slices, and losing vectored writes here would turn
834    // every response into an extra syscall.
835    fn poll_write_vectored(
836        mut self: std::pin::Pin<&mut Self>,
837        cx: &mut std::task::Context<'_>,
838        bufs: &[std::io::IoSlice<'_>],
839    ) -> std::task::Poll<std::io::Result<usize>> {
840        std::pin::Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)
841    }
842
843    fn is_write_vectored(&self) -> bool {
844        self.inner.is_write_vectored()
845    }
846}
847
848/// Per-connection settings, resolved once from the app config.
849///
850/// Grouped rather than passed individually: nine positional parameters is a
851/// call site nobody can read.
852#[derive(Clone, Copy)]
853struct ConnSettings {
854    max_body: usize,
855    timeout_ms: u64,
856    max_headers: usize,
857    header_read_timeout_ms: u64,
858    keep_alive_ms: u64,
859    h2_max_header_list_size: u32,
860    h2_max_concurrent_streams: u32,
861}
862
863impl ConnSettings {
864    fn from(cfg: &crate::app::ServerConfig) -> Self {
865        Self {
866            max_body: cfg.max_body_bytes,
867            timeout_ms: cfg.request_timeout_ms,
868            max_headers: cfg.max_headers,
869            header_read_timeout_ms: cfg.header_read_timeout_ms,
870            keep_alive_ms: cfg.keep_alive_ms,
871            h2_max_header_list_size: cfg.h2_max_header_list_size,
872            h2_max_concurrent_streams: cfg.h2_max_concurrent_streams,
873        }
874    }
875}
876
877/// Serve on several addresses at once, until `shutdown` resolves.
878///
879/// Useful for binding IPv4 and IPv6 separately, or for exposing an admin port
880/// alongside the public one. Each address gets its own accept loop; the first
881/// bind failure aborts, since a server that silently came up on half its
882/// addresses is worse than one that refused to start.
883pub async fn serve_many<F>(app: App, addrs: Vec<SocketAddr>, shutdown: F) -> std::io::Result<()>
884where
885    F: Future<Output = ()> + Send + 'static,
886{
887    if addrs.is_empty() {
888        return Err(std::io::Error::new(
889            std::io::ErrorKind::InvalidInput,
890            "no addresses to bind",
891        ));
892    }
893
894    // Bind everything first, so a failure is reported *before* any address
895    // starts serving. Binding inside the spawned tasks meant a failure was not
896    // observed until after `shutdown.await` — the process ran happily on the
897    // addresses that did work, said nothing, and surfaced the error only on the
898    // way out. That is exactly the half-up server the doc comment above rules
899    // out.
900    let mut listeners = Vec::with_capacity(addrs.len());
901    for addr in addrs {
902        match bind_tcp(addr, app.config().backlog) {
903            Ok(l) => listeners.push(l),
904            Err(e) => {
905                tracing::error!(%addr, error = %e, "bind failed; not starting");
906                return Err(e);
907            }
908        }
909    }
910
911    // One shutdown signal fans out to every loop.
912    let (tx, _) = tokio::sync::broadcast::channel::<()>(1);
913    let mut tasks = Vec::with_capacity(listeners.len());
914
915    // One budget for the process, not one per listener. Constructing it inside
916    // each accept loop multiplied `max_connections` and `max_tls_handshakes` by
917    // the number of bound addresses — binding v4 and v6 quietly doubled a cap
918    // documented as bounding "what the process serves at once".
919    let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
920
921    for listener in listeners {
922        let app = app.clone();
923        let limits = limits.clone();
924        let mut rx = tx.subscribe();
925        tasks.push(tokio::spawn(async move {
926            serve_listener(app, listener, limits, async move {
927                let _ = rx.recv().await;
928            })
929            .await
930        }));
931    }
932
933    shutdown.await;
934    let _ = tx.send(());
935
936    let mut first_err: Option<std::io::Error> = None;
937    for t in tasks {
938        let outcome = match t.await {
939            Ok(r) => r,
940            Err(e) => Err(std::io::Error::other(format!("listener task failed: {e}"))),
941        };
942        if let Err(e) = outcome {
943            first_err.get_or_insert(e);
944        }
945    }
946    match first_err {
947        Some(e) => Err(e),
948        None => Ok(()),
949    }
950}
951
952/// Serve on a Unix domain socket instead of TCP.
953///
954/// Useful behind a local reverse proxy: no TCP stack and no port to firewall.
955///
956/// The socket path is unlinked first if a *stale* file is present — a crashed
957/// process leaves one behind, and binding would otherwise fail forever. Stale
958/// is established rather than assumed: a socket node that still has a listener
959/// is left alone and the bind fails with [`AddrInUse`], so a second instance
960/// started on a path already in use refuses instead of taking it over.
961///
962/// # Permissions
963///
964/// The socket node is created under the process umask and this function does
965/// not chmod it. Do not rely on the node's own mode for access control: whether
966/// the permission bits on a socket are consulted at `connect(2)` is
967/// platform-dependent — Linux enforces them, some BSDs historically did not.
968/// What is enforced everywhere is path resolution, so put the socket in a
969/// directory whose permissions you control, and set the umask before calling if
970/// the node's own mode matters to you. Adjusting it afterwards is racy: the
971/// socket accepts connections from the moment it is bound.
972///
973/// Unix only; there is no Windows equivalent.
974///
975/// [`AddrInUse`]: std::io::ErrorKind::AddrInUse
976#[cfg(unix)]
977pub async fn serve_unix<F>(
978    app: App,
979    path: impl AsRef<std::path::Path>,
980    shutdown: F,
981) -> std::io::Result<()>
982where
983    F: Future<Output = ()> + Send + 'static,
984{
985    let path = path.as_ref();
986    // A leftover socket node from a crash blocks bind with EADDRINUSE, so one
987    // has to go — but only once it is known to be dead. The unlink used to be
988    // unconditional, which meant a second `serve_unix` on a path an instance
989    // was already serving deleted that instance's node and bound its own: the
990    // first process kept running on an inode nothing could reach any more,
991    // still reporting itself healthy, while every new connection went to the
992    // second. Probing with a connect distinguishes the two cases the way any
993    // other server does it — a refused connection means nobody is listening,
994    // and an accepted one means the path is genuinely in use.
995    // A Unix socket carries no TLS, and this listener never consulted the
996    // setting — so an application configured with `tls` served *plaintext* here
997    // while `apply_security_headers` kept asserting HSTS on every response,
998    // because that gate reads `config.tls.is_some()` rather than the transport.
999    // The result was a cleartext service telling its clients it was
1000    // HTTPS-only.
1001    //
1002    // Refused rather than downgraded. The two settings contradict each other —
1003    // "terminate TLS with this certificate" and "listen on a Unix socket" cannot
1004    // both be honoured — and silently picking one is how the mismatch went
1005    // unnoticed. An application fronted by a TLS-terminating proxy should not be
1006    // configuring `tls` at all; one that is has said something it does not mean.
1007    if app.config().tls.is_some() {
1008        return Err(std::io::Error::new(
1009            std::io::ErrorKind::InvalidInput,
1010            "this app is configured for TLS, which a Unix socket cannot carry: \
1011             serving it here would be cleartext while still advertising HSTS. \
1012             Drop the `tls` configuration to serve over a Unix socket, or use \
1013             `serve`/`start` to terminate TLS on a TCP listener.",
1014        ));
1015    }
1016
1017    unlink_if_stale(path).await?;
1018
1019    let listener = bind_unix(path, app.config().backlog)?;
1020    // Remember which inode this bind produced. At shutdown the node at `path`
1021    // may no longer be ours, and removing whatever happens to be there then
1022    // would leave a live successor serving a path with no socket on it. See
1023    // the unlink at the end of this function.
1024    let bound = socket_identity(path);
1025    let conn_cfg = ConnSettings::from(app.config());
1026    let shutdown_timeout_ms = app.config().shutdown_timeout_ms;
1027
1028    let drain = Drain::new();
1029    let limits = std::sync::Arc::new(AcceptLimits::from(app.config()));
1030    let mut backoff = AcceptBackoff::default();
1031    let mut shutdown = std::pin::pin!(shutdown);
1032
1033    // A Unix peer has no IP; report the loopback address so `peer_addr` has a
1034    // consistent shape rather than being absent only on this transport.
1035    let peer = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
1036
1037    loop {
1038        // Same shape as the TCP loop: a saturated budget must not outrank the
1039        // shutdown signal. See the comment there.
1040        let acquire = async {
1041            let slot = limits.connection_slot().await;
1042            (slot, listener.accept().await)
1043        };
1044
1045        tokio::select! {
1046            biased;
1047            _ = &mut shutdown => break,
1048            (slot, accepted) = acquire => {
1049                let (stream, _) = match accepted {
1050                    Ok(s) => {
1051                        backoff.reset();
1052                        s
1053                    }
1054                    Err(e) => {
1055                        let pause = backoff.next_pause();
1056                        tracing::warn!(error = %e, pause_ms = pause.as_millis() as u64, "accept failed");
1057                        tokio::time::sleep(pause).await;
1058                        continue;
1059                    }
1060                };
1061                serve_stream(app.clone(), stream, conn_cfg, peer, drain.token(), slot).await;
1062            }
1063        }
1064    }
1065
1066    drain.wait(shutdown_timeout_ms).await;
1067    // Leave no socket file behind for the next start to trip over — but only
1068    // our own. If something else has taken the path over in the meantime, the
1069    // node sitting there belongs to a listener that is still serving, and
1070    // deleting it would take the successor off the air without either process
1071    // noticing: it goes on accepting on an inode with no name, and the next
1072    // client to resolve the path finds nothing. Comparing device and inode is
1073    // exact where comparing paths is not.
1074    if bound.is_some() && bound == socket_identity(path) {
1075        let _ = std::fs::remove_file(path);
1076    }
1077    Ok(())
1078}
1079
1080/// Identify the node currently at `path` by device and inode.
1081///
1082/// `None` means there is nothing there, or that it could not be stated — either
1083/// way it is not something this process should claim, so the callers treat it
1084/// as "not ours". `symlink_metadata` rather than `metadata`: a symlink pointing
1085/// at the socket is a different node from the socket, and following it would
1086/// let a link swapped in under us stand in for the real thing.
1087#[cfg(unix)]
1088fn socket_identity(path: &std::path::Path) -> Option<(u64, u64)> {
1089    use std::os::unix::fs::MetadataExt;
1090    let md = std::fs::symlink_metadata(path).ok()?;
1091    Some((md.dev(), md.ino()))
1092}
1093
1094/// Clear `path` for a fresh bind, refusing to disturb a socket still in use.
1095///
1096/// Returns `AddrInUse` when the path already has a listener behind it. Anything
1097/// else there is removed, as before: a socket node that refuses connections is
1098/// the leftover this exists to clean up, and a non-socket file at the path is
1099/// removed too, because that is the behaviour `serve_unix` has always had and
1100/// bind would fail on it regardless.
1101#[cfg(unix)]
1102async fn unlink_if_stale(path: &std::path::Path) -> std::io::Result<()> {
1103    use std::os::unix::fs::FileTypeExt;
1104
1105    let Ok(md) = std::fs::symlink_metadata(path) else {
1106        return Ok(()); // nothing in the way
1107    };
1108
1109    // Only a socket can have a listener, so only a socket is worth probing.
1110    // The probe is a real `connect(2)`, which is the only way to tell a live
1111    // socket from an abandoned node: both look identical on disk, since neither
1112    // a crash nor a plain `drop` of the listener unlinks the name.
1113    if md.file_type().is_socket() && tokio::net::UnixStream::connect(path).await.is_ok() {
1114        return Err(std::io::Error::new(
1115            std::io::ErrorKind::AddrInUse,
1116            format!(
1117                "{} is already being served by another listener",
1118                path.display()
1119            ),
1120        ));
1121    }
1122
1123    // The removal is reported now rather than swallowed, because the bind that
1124    // follows would fail with a bare EADDRINUSE that says nothing about why the
1125    // path could not be cleared. A node that vanished between the stat and the
1126    // removal is not a failure: the path is clear, which is all this wanted.
1127    match std::fs::remove_file(path) {
1128        Err(e) if e.kind() != std::io::ErrorKind::NotFound => Err(e),
1129        _ => Ok(()),
1130    }
1131}
1132
1133async fn serve_stream<S>(
1134    app: App,
1135    stream: S,
1136    cfg: ConnSettings,
1137    peer: std::net::SocketAddr,
1138    token: DrainToken,
1139    slot: ConnSlot,
1140) where
1141    S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
1142{
1143    // Taken as the raw stream rather than as an already-wrapped `TokioIo` so
1144    // the preface sniffing can be watched underneath it — see `Sniffing`.
1145    let negotiated = std::sync::Arc::new(Negotiated::default());
1146    let io = TokioIo::new(Sniffing::new(stream, negotiated.clone()));
1147
1148    // The idle watchdog needs to know when this connection last did anything.
1149    // hyper exposes no per-request hook, but the service *is* the per-request
1150    // hook: every request on this connection passes through the closure below.
1151    let activity = std::sync::Arc::new(ConnActivity::new());
1152    // Subscribe before the token is moved into the guard: the connection loop
1153    // watches the shutdown signal, the guard owns the drain's release side.
1154    let mut signal = token.signal.clone();
1155    // One share of the budget for this connection, cloned into any upgraded
1156    // socket so the permit outlives the HTTP connection future.
1157    let guard = ConnGuard::new(slot, token);
1158
1159    let svc = {
1160        let activity = activity.clone();
1161        let guard = guard.clone();
1162        service_fn(move |req: HyperRequest<Incoming>| {
1163            let app = app.clone();
1164            let activity = activity.clone();
1165            let conn_guard = guard.clone();
1166            async move {
1167                // Busy, not idle: a handler slower than the keep-alive period
1168                // must not have its own connection closed underneath it.
1169                //
1170                // An RAII guard rather than paired begin/end calls, because a
1171                // request future can be *dropped* mid-await — an HTTP/2 client
1172                // sending RST_STREAM makes hyper do exactly that — and a missed
1173                // decrement pinned the connection as busy forever, exempt from
1174                // the idle watchdog and from the shutdown linger.
1175                let in_flight = InFlight::new(activity);
1176                let res = handle(app, req, cfg.max_body, cfg.timeout_ms, peer, conn_guard).await;
1177                // The guard rides on the response body: the connection is still
1178                // working while bytes are being written, and releasing it when
1179                // the handler returned meant a large download or an SSE stream
1180                // counted as idle and was dropped during shutdown.
1181                res.map(|r| r.map(|body| attach_guard(body, in_flight)))
1182            }
1183        })
1184    };
1185    // One builder that negotiates HTTP/1.1 or HTTP/2 per connection: h2 over
1186    // TLS via ALPN, and h2c prior-knowledge in plaintext. The v1 engine used
1187    // `http1::Builder` directly with a note that `auto::Builder`'s
1188    // `serve_connection` returned a future the spawn closure could not own —
1189    // `serve_connection_with_upgrades` returns an owned one, which is what
1190    // makes this possible while keeping the WebSocket upgrade path working.
1191    let mut builder =
1192        hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new());
1193    builder.http1().max_headers(cfg.max_headers);
1194    // `max_headers` is HTTP/1 only — it counts headers, and h2 has no count,
1195    // only an encoded size. Both protocols need bounding, and one knob cannot
1196    // do it.
1197    builder
1198        .http2()
1199        .max_header_list_size(cfg.h2_max_header_list_size);
1200    // An h2 connection multiplexes many requests, so without this one
1201    // connection is an unbounded amount of concurrent work.
1202    builder.http2().max_concurrent_streams(
1203        (cfg.h2_max_concurrent_streams > 0).then_some(cfg.h2_max_concurrent_streams),
1204    );
1205    // `0` means no connection reuse: answer and close. Any other value means
1206    // reuse *bounded by an idle timeout*, which hyper has no knob for — see
1207    // the watchdog branch in the connection loop below.
1208    builder.http1().keep_alive(cfg.keep_alive_ms > 0);
1209    // Without this a client can hold a connection open indefinitely by
1210    // dribbling header bytes: the per-request timeout cannot help, because
1211    // there is no request until the header block is complete.
1212    if cfg.header_read_timeout_ms > 0 {
1213        // hyper panics if a timeout is configured without a timer to drive it.
1214        let deadline = std::time::Duration::from_millis(cfg.header_read_timeout_ms);
1215        builder.http1().timer(hyper_util::rt::TokioTimer::new());
1216        builder.http1().header_read_timeout(deadline);
1217        builder.http2().timer(hyper_util::rt::TokioTimer::new());
1218        // HTTP/2 has no header-read deadline to set: a header block arrives in
1219        // frames on an already-open connection, so hyper offers no equivalent
1220        // knob. Its equivalent question — is this peer actually still there? —
1221        // is answered by a keep-alive PING, which hyper leaves disabled by
1222        // default. Without it the documented slow-loris defence covered only
1223        // one of the two protocols served on the port: a peer that completed
1224        // the preface and then dribbled a partial HEADERS frame was bounded by
1225        // nothing but the idle watchdog, at `keep_alive_ms` rather than at the
1226        // 10s this knob advertises, while holding a connection permit.
1227        //
1228        // Ping at the deadline, drop at the deadline again, so a stalled peer
1229        // is gone within roughly twice the configured value and a live one that
1230        // simply has nothing to say answers and stays.
1231        builder.http2().keep_alive_interval(deadline);
1232        builder.http2().keep_alive_timeout(deadline);
1233    }
1234
1235    // The connection borrows the builder, which is the ownership problem the v1
1236    // engine hit and worked around by using `http1::Builder` instead. Moving
1237    // the builder into the task resolves it: the task owns both.
1238    tokio::spawn(async move {
1239        // `with_upgrades` is required for WebSockets and harmless otherwise: an
1240        // HTTP/2 connection never carries an HTTP/1 upgrade.
1241        let conn = builder.serve_connection_with_upgrades(io, svc);
1242        let mut conn = std::pin::pin!(conn);
1243        let mut winding_down = false;
1244
1245        // The idle deadline. Re-armed on every wake that finds the connection
1246        // busy or recently active, so the common case costs one timer per
1247        // connection rather than one per request.
1248        //
1249        // `keep_alive_ms == 0` is handled by its own branch below rather than by
1250        // this timer, because it is not a deadline: it means "answer and close",
1251        // so the moment to act is a request finishing, not a period elapsing.
1252        let idle_enabled = cfg.keep_alive_ms > 0;
1253        let zero_keep_alive = cfg.keep_alive_ms == 0;
1254        let idle = tokio::time::sleep(std::time::Duration::from_millis(if idle_enabled {
1255            cfg.keep_alive_ms
1256        } else {
1257            // Parked: the branch is disabled, but `select!` still needs a
1258            // future to name.
1259            u64::MAX / 2
1260        }));
1261        let mut idle = std::pin::pin!(idle);
1262
1263        // Parked until a shutdown signal arms it; see the signal branch below.
1264        let linger = tokio::time::sleep(std::time::Duration::from_secs(3_600));
1265        let mut linger = std::pin::pin!(linger);
1266        let mut lingering = false;
1267
1268        // The deadline for choosing a protocol at all. Both header deadlines
1269        // configured above belong to a connection hyper-util has not built yet,
1270        // and the sniffing it does first carries no timer, so this is the only
1271        // thing standing between a silent socket and a permit held for the life
1272        // of the process — the idle watchdog is a backstop at `keep_alive_ms`
1273        // rather than at the advertised deadline, and at `keep_alive_ms` of 0 it
1274        // deliberately waits for a first response before closing anything, so it
1275        // does not cover this phase either.
1276        //
1277        // It stops applying at negotiation, not at the first request: an HTTP/2
1278        // client that has handshaken is entitled to idle for far longer than
1279        // the header deadline, and the h2 keep-alive ping is what asks whether
1280        // it is still there.
1281        let mut negotiation_armed = cfg.header_read_timeout_ms > 0;
1282        let negotiation =
1283            tokio::time::sleep(std::time::Duration::from_millis(if negotiation_armed {
1284                cfg.header_read_timeout_ms
1285            } else {
1286                // Parked, like the linger above: the branch is disabled, but
1287                // `select!` still needs a future to name.
1288                u64::MAX / 2
1289            }));
1290        let mut negotiation = std::pin::pin!(negotiation);
1291
1292        loop {
1293            tokio::select! {
1294                // Bias the connection: with both branches ready, finishing the
1295                // request in hand beats re-checking a signal we have already
1296                // acted on.
1297                biased;
1298                _ = conn.as_mut() => break,
1299                // `wait_for` rather than `changed` so a signal that fired
1300                // before this task got scheduled is still seen.
1301                _ = signal.wait_for(|fired| *fired), if !winding_down => {
1302                    winding_down = true;
1303                    // Finish the in-flight request, refuse further ones on this
1304                    // connection, then let the loop poll it to completion.
1305                    conn.as_mut().graceful_shutdown();
1306
1307                    // A connection with nothing in flight has nothing to drain,
1308                    // but it will not necessarily close on its own: an idle
1309                    // HTTP/2 connection is shut down by sending GOAWAY and then
1310                    // waiting for the *peer* to close, which an idle peer never
1311                    // does. Left alone, every shutdown costs the full grace
1312                    // period — a rolling restart would pay it every time.
1313                    //
1314                    // So give the GOAWAY (or the HTTP/1 close) a brief window to
1315                    // reach the wire and then stop. Only for connections with no
1316                    // request in flight; a busy one still gets the full grace.
1317                    if !activity.busy() {
1318                        linger.as_mut().reset(
1319                            tokio::time::Instant::now() + GOAWAY_LINGER,
1320                        );
1321                        lingering = true;
1322                    }
1323                }
1324                // The linger elapsed on an idle connection: drop it.
1325                _ = linger.as_mut(), if lingering => {
1326                    if !activity.busy() {
1327                        break;
1328                    }
1329                    // A request arrived between arming this and now — rare.
1330                    // Re-check later rather than clearing `winding_down`, which
1331                    // would let the shutdown branch fire again and turn this
1332                    // into a 250ms cycle of repeated `graceful_shutdown` calls.
1333                    // The grace period still bounds the wait overall.
1334                    linger
1335                        .as_mut()
1336                        .reset(tokio::time::Instant::now() + GOAWAY_LINGER);
1337                }
1338                // The peer never got as far as a protocol. Wound down the same
1339                // way the idle watchdog does rather than dropped outright, so
1340                // the socket is closed by hyper and anything already buffered
1341                // reaches the wire.
1342                _ = negotiation.as_mut(), if negotiation_armed && !winding_down => {
1343                    // `select!` evaluates a branch's precondition when it is
1344                    // *entered*, not when the branch fires. This one is entered
1345                    // before the peer has sent anything, so `negotiated` cannot
1346                    // be tested there: a connection that handshakes a
1347                    // millisecond later still arrives here with the branch
1348                    // armed, and closing it would drop every live HTTP/2 client
1349                    // that idles past the header deadline — which is exactly
1350                    // what `a_responsive_http2_client_is_not_dropped` exists to
1351                    // catch. Read the flag here, where it is current.
1352                    if negotiated.get() {
1353                        // Disarms the branch on the next pass, so a deadline
1354                        // that has already elapsed does not keep waking us.
1355                        negotiation_armed = false;
1356                    } else {
1357                        tracing::debug!(
1358                            %peer,
1359                            deadline_ms = cfg.header_read_timeout_ms,
1360                            "no protocol chosen within the header deadline; closing"
1361                        );
1362                        winding_down = true;
1363                        conn.as_mut().graceful_shutdown();
1364                        // Nothing has been served, so there is nothing to drain
1365                        // — but an idle connection does not necessarily close
1366                        // on its own, which is what this linger is for
1367                        // elsewhere too.
1368                        linger
1369                            .as_mut()
1370                            .reset(tokio::time::Instant::now() + GOAWAY_LINGER);
1371                        lingering = true;
1372                    }
1373                }
1374                // `keep_alive_ms == 0` means "answer and close". hyper does that
1375                // for HTTP/1 via `keep_alive(false)` above and has no h2
1376                // counterpart, and the idle watchdog used to be switched off
1377                // entirely at 0 — so an HTTP/2 connection got neither, and was
1378                // held for the life of the process. That is the "never expires"
1379                // reading `http3::idle_ms_for` refused for QUIC, and it made the
1380                // strictest setting available *weaker* than the 75s default.
1381                //
1382                // Woken by a request finishing rather than by a timer: the moment
1383                // to close is when nothing is in flight, and `InFlight`'s `Drop`
1384                // is what knows when that is. A timer would have to guess an
1385                // interval, and the obvious small one costs a wake every interval
1386                // for as long as a handler runs — measured at ~40/second against a
1387                // one-second handler, and `request_timeout_ms` allows thirty. This
1388                // is one wake per completed request instead.
1389                //
1390                // Gated on `has_served` so the pre-protocol phase stays the
1391                // negotiation deadline's job. A connection that negotiates and
1392                // never sends a request is left to the h2 keep-alive ping, as at
1393                // any other value of this knob.
1394                //
1395                // `!busy()` is belt-and-braces rather than what prevents a
1396                // truncation, and worth saying so: the wake arrives *after* the
1397                // finishing request's own count is gone, so with one request in
1398                // flight it is true either way, and an HTTP/2 connection that has
1399                // several would still drain them — `graceful_shutdown` finishes
1400                // in-flight streams, and the linger below re-checks `busy()`
1401                // before it breaks. Keeping the condition here means this branch
1402                // states its own precondition instead of inheriting it from
1403                // hyper's drain semantics and a check two branches away, either of
1404                // which could change without this one noticing.
1405                _ = activity.request_finished(), if zero_keep_alive && !winding_down => {
1406                    if activity.has_served() && !activity.busy() {
1407                        winding_down = true;
1408                        conn.as_mut().graceful_shutdown();
1409                        linger
1410                            .as_mut()
1411                            .reset(tokio::time::Instant::now() + GOAWAY_LINGER);
1412                        lingering = true;
1413                    }
1414                }
1415                _ = idle.as_mut(), if idle_enabled && !winding_down => {
1416                    match activity.idle_for(cfg.keep_alive_ms) {
1417                        // Nothing in flight and nothing recent: close it.
1418                        None => {
1419                            winding_down = true;
1420                            conn.as_mut().graceful_shutdown();
1421                            // Arm the linger here too. `graceful_shutdown` on an
1422                            // HTTP/2 connection sends GOAWAY and then waits for
1423                            // the peer, which an idle peer never answers — and
1424                            // with `winding_down` set every other branch is
1425                            // gated off, so the loop had nothing left to poll
1426                            // but a future that never resolves. The connection
1427                            // and its permit were then held for the life of the
1428                            // process; at the default cap that is a server that
1429                            // stops accepting anything at all.
1430                            linger
1431                                .as_mut()
1432                                .reset(tokio::time::Instant::now() + GOAWAY_LINGER);
1433                            lingering = true;
1434                        }
1435                        // Busy or recently active — wait out the remainder.
1436                        Some(remaining) => idle
1437                            .as_mut()
1438                            .reset(tokio::time::Instant::now() + remaining),
1439                    }
1440                }
1441            }
1442        }
1443        // The guard drops here — but only *this* clone. An upgraded WebSocket
1444        // holds its own, so the permit and the drain token are returned when
1445        // the socket task ends rather than when the 101 was dispatched.
1446        drop(guard);
1447    });
1448}
1449
1450/// Reported when the grace period expires with requests still in flight.
1451///
1452/// Not an error: exiting on time is the point, and the alternative is hanging.
1453fn tracing_drain_timeout(ms: u64) {
1454    tracing::warn!(
1455        grace_ms = ms,
1456        "graceful shutdown timed out; exiting with requests in flight"
1457    );
1458}
1459
1460/// Serve one request, and harden whatever comes back on the way out.
1461///
1462/// The hardening is here rather than only inside `respond` because `respond`
1463/// has several exits and two of them never reach the pipeline: a refusal is
1464/// composed and returned before `process_call` is ever called, so the
1465/// `SecurityHeaders` middleware — which is a pipeline middleware — could not
1466/// see it. A `413` for an oversized upload therefore went out with nothing but
1467/// `Connection: close`, while every other status the same server produced,
1468/// including a plain `404`, carried the full set.
1469///
1470/// Wrapping the whole function rather than patching each refusal is what keeps
1471/// that from happening again: any exit added later passes through here. It is
1472/// safe to run over a pipeline response too, because a header already present
1473/// is left alone, so the second application is a no-op.
1474async fn handle(
1475    app: App,
1476    req: HyperRequest<Incoming>,
1477    max_body: usize,
1478    timeout_ms: u64,
1479    peer: std::net::SocketAddr,
1480    conn_guard: ConnGuard,
1481) -> Result<HyperResponse<UnsyncBoxBody<Bytes, std::io::Error>>, Infallible> {
1482    let mut res = respond(app.clone(), req, max_body, timeout_ms, peer, conn_guard).await?;
1483    // `false`: this transport cannot tell TLS from plaintext by itself — the
1484    // stream arrives already decrypted, or never was — so the builder's own
1485    // certificate is the only evidence, and `apply_security_headers` consults
1486    // it. HTTP/3 is the case that can say `true`.
1487    app.apply_security_headers(res.headers_mut(), false);
1488    Ok(res)
1489}
1490
1491/// Turn one hyper request into one hyper response.
1492///
1493/// Everything below is about *whether* to dispatch: the two refusals return
1494/// without ever reaching the pipeline. `handle` above is what makes the answer
1495/// look the same either way.
1496async fn respond(
1497    app: App,
1498    req: HyperRequest<Incoming>,
1499    max_body: usize,
1500    timeout_ms: u64,
1501    peer: std::net::SocketAddr,
1502    conn_guard: ConnGuard,
1503) -> Result<HyperResponse<UnsyncBoxBody<Bytes, std::io::Error>>, Infallible> {
1504    // Refuse a body the client has already declared too large, before the
1505    // request is dispatched.
1506    //
1507    // Streaming made the cap lazy: `Limited` only trips when something reads
1508    // the body, so a handler that ignores it — or middleware that
1509    // short-circuits before an extractor runs — answered `200` for a request
1510    // the server had declared it would refuse. `Content-Length` is the client's
1511    // own statement of size, so this costs one header lookup and needs no
1512    // buffering. A chunked body still declares nothing and remains bounded by
1513    // the stream limit at the point it is read.
1514    if let Some(declared) = req
1515        .headers()
1516        .get(http::header::CONTENT_LENGTH)
1517        .and_then(|v| v.to_str().ok())
1518        .and_then(|v| v.parse::<u64>().ok())
1519    {
1520        if declared > max_body as u64 {
1521            let res = HyperResponse::builder()
1522                .status(StatusCode::PAYLOAD_TOO_LARGE)
1523                // Nothing will read the body, so the connection cannot be
1524                // reused: whatever the client is still sending would be read as
1525                // the next request.
1526                .header(http::header::CONNECTION, "close")
1527                // Every response this server composes says what its body is,
1528                // and this one did not: it went out as seventeen unlabelled
1529                // bytes, leaving the client's own sniffing to decide. Naming
1530                // the type is what the accompanying `nosniff` is there to
1531                // enforce, and the pair only means something together.
1532                .header(http::header::CONTENT_TYPE, TEXT_PLAIN)
1533                .body(into_boxed_body(Body::Bytes(bytes::Bytes::from_static(
1534                    b"Payload Too Large",
1535                ))))
1536                .expect("response build is infallible");
1537            return Ok(res);
1538        }
1539    }
1540
1541    // Only the WebSocket branch below consumes this. Discarded explicitly
1542    // rather than silencing the whole function, which would also hide a
1543    // genuinely unused variable.
1544    #[cfg(not(feature = "ws"))]
1545    let _ = &conn_guard;
1546
1547    // RFC 9112 §6.3 rule 3: a request carrying both `Transfer-Encoding` and
1548    // `Content-Length` is framed by the transfer coding, and "ought to be
1549    // handled as an error" — no legitimate client sends both.
1550    //
1551    // The danger is not what Churust does with the message; it is what an
1552    // intermediary in front of Churust did with it. A proxy that believes the
1553    // `Content-Length` forwards a different number of body bytes than this
1554    // server consumes, and the leftovers become the start of the *next* request
1555    // on a reused connection — request smuggling. Since we cannot know what is
1556    // upstream, refuse the ambiguity and close, which removes the desync
1557    // surface whatever the proxy decided.
1558    //
1559    // This only fires below hyper 1.11. From 1.11 hyper removes the
1560    // `Content-Length` from the parsed header map itself, so nothing here can
1561    // observe the ambiguity — and there is no supported way to ask for the
1562    // headers as they arrived (`HeaderCaseMap` is `pub(crate)`). Recovering the
1563    // `400` would mean parsing HTTP/1 message framing off the socket ahead of
1564    // hyper, which is a second framing implementation guarding the seam between
1565    // two framing implementations. Not worth it, because 1.11 also sets
1566    // `keep_alive = false` for this exact shape: the message is served, framed
1567    // by the transfer coding, and the connection closes — which is the desync
1568    // removal this branch existed to buy. `hyper = "1"` still resolves to
1569    // pre-1.11 versions, where it is the only thing standing there, so it stays.
1570    if req.headers().contains_key(http::header::TRANSFER_ENCODING)
1571        && req.headers().contains_key(http::header::CONTENT_LENGTH)
1572    {
1573        tracing::warn!(
1574            path = %req.uri().path(),
1575            "rejected a request with both Transfer-Encoding and Content-Length"
1576        );
1577        let res = HyperResponse::builder()
1578            .status(StatusCode::BAD_REQUEST)
1579            // Closing is the point: leaving the connection open would let
1580            // whatever the peer framed differently be read as a new request.
1581            .header(http::header::CONNECTION, "close")
1582            // As above: a body with no declared type is one the recipient has
1583            // to guess at.
1584            .header(http::header::CONTENT_TYPE, TEXT_PLAIN)
1585            .body(into_boxed_body(Body::Bytes(bytes::Bytes::from_static(
1586                b"Bad Request",
1587            ))))
1588            .expect("response build is infallible");
1589        return Ok(res);
1590    }
1591
1592    // RFC 9112 §3.2: an HTTP/1.1 request must carry exactly one `Host`, and a
1593    // server must answer `400` to one that carries none or several.
1594    //
1595    // The reason is the same as the framing check above, and so is the risk. A
1596    // request with two `Host` fields is one where this server and an
1597    // intermediary can disagree about which site it was for — the intermediary
1598    // routes or authorizes on one, the origin serves the other — and a request
1599    // with none leaves `Call::host` and every `guard::host` route deciding on
1600    // nothing at all.
1601    //
1602    // Gated on the version, because the other transports do not carry it there:
1603    // HTTP/2 and HTTP/3 replaced the field with `:authority`, which hyper and h3
1604    // put in the URI, and an ungated check would refuse every request on them.
1605    // HTTP/1.0 predates the requirement. An absolute-form target is accepted
1606    // without the field for the same reason `Call::host` prefers the URI: the
1607    // authority is already there and it is the one that wins.
1608    if req.version() == http::Version::HTTP_11 {
1609        let hosts = req.headers().get_all(http::header::HOST).iter().count();
1610        let bad = hosts > 1 || (hosts == 0 && req.uri().authority().is_none());
1611        if bad {
1612            tracing::warn!(
1613                path = %req.uri().path(),
1614                hosts,
1615                "rejected an HTTP/1.1 request without exactly one Host"
1616            );
1617            let res = HyperResponse::builder()
1618                .status(StatusCode::BAD_REQUEST)
1619                // Closing for the same reason as the framing refusal: the
1620                // disagreement is about what the message meant, so nothing after
1621                // it on this connection can be trusted to mean what it says.
1622                .header(http::header::CONNECTION, "close")
1623                .header(http::header::CONTENT_TYPE, TEXT_PLAIN)
1624                .body(into_boxed_body(Body::Bytes(bytes::Bytes::from_static(
1625                    b"Bad Request",
1626                ))))
1627                .expect("response build is infallible");
1628            return Ok(res);
1629        }
1630    }
1631
1632    #[cfg(feature = "ws")]
1633    let ws_max_frame_bytes = app.config().ws_max_frame_bytes;
1634    #[cfg(feature = "ws")]
1635    let ws_max_message_bytes = app.config().ws_max_message_bytes;
1636    #[cfg(feature = "ws")]
1637    let ws_idle_timeout_ms = app.config().ws_idle_timeout_ms;
1638    #[cfg(feature = "ws")]
1639    let mut req = req;
1640    #[cfg(feature = "ws")]
1641    let on_upgrade = if crate::ws::is_upgrade_request(req.headers()) {
1642        Some(hyper::upgrade::on(&mut req))
1643    } else {
1644        None
1645    };
1646
1647    let (parts, body) = req.into_parts();
1648
1649    // The body is handed to the handler as a stream rather than collected here.
1650    // Collecting first made `max_body_bytes` a hard ceiling on upload size and
1651    // made memory scale with concurrent uploads. `Limited` still enforces the
1652    // cap; exceeding it now surfaces as an error item in the stream, which
1653    // `Call::try_receive_bytes` turns back into `413` for handlers that buffer.
1654    let limited = Limited::new(body, max_body);
1655    let body_stream: crate::call::BodyStream = Box::pin(BodyDataStream::new(limited).map(|r| {
1656        r.map_err(|e| {
1657            // Match the error's *type*, not its message. The message belongs
1658            // to http-body-util and can change in a patch release, which would
1659            // silently turn every oversized body into a `400`.
1660            if e.downcast_ref::<http_body_util::LengthLimitError>()
1661                .is_some()
1662            {
1663                crate::Error::new(StatusCode::PAYLOAD_TOO_LARGE, "request body too large")
1664            } else {
1665                crate::Error::bad_request(format!("error reading request body: {e}"))
1666            }
1667        })
1668    }));
1669
1670    #[cfg(feature = "ws")]
1671    let process = {
1672        let mut extensions = http::Extensions::new();
1673        extensions.insert(crate::call::PeerAddr(peer));
1674        if let Some(on_upgrade) = on_upgrade {
1675            extensions.insert(crate::ws::OnUpgradeHandle::new(on_upgrade));
1676            // So the upgraded socket keeps this connection's budget share.
1677            extensions.insert(conn_guard.clone());
1678            extensions.insert(crate::ws::WsIdleTimeout(ws_idle_timeout_ms));
1679            extensions.insert(crate::ws::WsLimits {
1680                max_frame_bytes: ws_max_frame_bytes,
1681                max_message_bytes: ws_max_message_bytes,
1682            });
1683        }
1684        app.process_call(
1685            crate::call::Call::new(parts.method, parts.uri, parts.headers, bytes::Bytes::new())
1686                .with_body_stream(body_stream),
1687            extensions,
1688        )
1689    };
1690    #[cfg(not(feature = "ws"))]
1691    let process = {
1692        let mut extensions = http::Extensions::new();
1693        extensions.insert(crate::call::PeerAddr(peer));
1694        app.process_call(
1695            crate::call::Call::new(parts.method, parts.uri, parts.headers, bytes::Bytes::new())
1696                .with_body_stream(body_stream),
1697            extensions,
1698        )
1699    };
1700
1701    let res = if timeout_ms == 0 {
1702        process.await
1703    } else {
1704        match tokio::time::timeout(std::time::Duration::from_millis(timeout_ms), process).await {
1705            Ok(res) => res,
1706            Err(_) => crate::response::Response::text("Request Timeout")
1707                .with_status(StatusCode::REQUEST_TIMEOUT),
1708        }
1709    };
1710
1711    let mut builder = HyperResponse::builder().status(res.status);
1712    if let Some(headers) = builder.headers_mut() {
1713        *headers = res.headers;
1714    }
1715    Ok(builder
1716        .body(into_boxed_body(res.body))
1717        .expect("response build is infallible"))
1718}