saluki_io/net/server/
http.rs

1//! HTTP server.
2
3use std::{
4    convert::Infallible,
5    future::Future,
6    pin::Pin,
7    sync::{Arc, Mutex},
8    task::{Context, Poll},
9    time::Duration,
10};
11
12use async_trait::async_trait;
13use axum::{response::IntoResponse, Router};
14use http::Request;
15use hyper::rt::{Read, Write};
16use hyper_util::{
17    rt::{TokioIo, TokioTimer},
18    server::conn::auto::Builder,
19};
20use pin_project_lite::pin_project;
21use rustls::ServerConfig;
22use saluki_common::sync::shutdown::ShutdownHandle;
23use saluki_core::runtime::{
24    self,
25    state::{DataspaceRegistry, Identifier},
26    BuilderState, ChildBuilder, InitializationError, ShutdownStrategy, Supervisable, Supervisor, SupervisorFuture,
27    SupervisorHandle,
28};
29use saluki_error::{ErrorContext as _, GenericError};
30use saluki_tls::ensure_server_config_fips_compliant;
31use stringtheory::MetaString;
32use tokio::{
33    pin,
34    runtime::Handle,
35    select,
36    time::{sleep, timeout, Sleep},
37};
38use tokio_rustls::TlsAcceptor;
39use tonic::{body::Body as GrpcBody, server::NamedService, service::Routes};
40use tower::{util::Oneshot, Service, ServiceExt as _};
41use tracing::{debug, error, info, warn};
42
43use crate::net::{
44    listener::ConnectionOrientedListener, server::grpc::merge_grpc_routes, stream::Connection, ListenAddress,
45};
46
47/// Conventional gRPC keepalive interval: how long a connection sits idle before the server sends a PING.
48const DEFAULT_GRPC_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(2 * 60 * 60);
49
50/// Conventional gRPC keepalive timeout: how long the server waits for a PONG before closing the connection.
51const DEFAULT_GRPC_KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(20);
52
53/// Default server name.
54const DEFAULT_SERVER_NAME: &str = "http_server";
55
56/// How long a connection is given to drain when no timeout is configured.
57///
58/// Matches the default shutdown timeout a topology gives its components, which is the deadline the rest of the process
59/// is built around.
60const DEFAULT_DRAIN_DEADLINE: Duration = Duration::from_secs(30);
61
62/// How much longer than the drain deadline the subtree's shutdown budget runs.
63///
64/// A connection bounds its own drain, so under normal circumstances it finishes and exits cleanly before the budget is
65/// anywhere near elapsing. The slack keeps the two from racing: without it a connection that took its full deadline
66/// could be force-aborted at the very moment it was about to return, which would be reported as an unclean shutdown
67/// all the way up the tree. The budget is the backstop for a connection that ignores its own deadline entirely.
68const SHUTDOWN_BUDGET_SLACK: Duration = Duration::from_secs(1);
69
70/// How long a TLS handshake is given to complete before the connection is abandoned.
71///
72/// Running handshakes concurrently is what keeps a slow one from holding up the listener, but it also means a peer
73/// that connects and then says nothing no longer blocks anything -- and so nothing would ever reclaim it. This bounds
74/// that. It matches both the HTTP/1.1 header read timeout here and the default handshake deadline on the client side
75/// ([`with_tls_handshake_timeout`][crate::net::client::http::HttpClientBuilder::with_tls_handshake_timeout]), since all
76/// three cover the same shape of problem: a peer that opens a connection and never finishes what it started.
77const TLS_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
78
79/// Process name for the child that accepts connections.
80const ACCEPTOR_TASK_NAME: &str = "acceptor";
81
82/// Process name for the child that performs a single TLS handshake.
83const TLS_HANDSHAKE_TASK_NAME: &str = "tls_handshake";
84
85/// Process name for the child that serves a single connection.
86const CONNECTION_TASK_NAME: &str = "http_conn";
87
88/// Process name for a background child spawned on `hyper`'s behalf.
89const CONNECTION_BG_TASK_NAME: &str = "http_conn_task";
90
91/// The connection builder this server uses, specialized to our own executor.
92type ConnBuilder = Builder<SupervisedExecutor>;
93
94/// HTTP/2 connection settings.
95///
96/// These cover the HTTP/2 keepalive mechanism, the maximum lifetime of an individual connection, and how many
97/// concurrent streams a connection may carry. They apply only to connections actually served over HTTP/2; a connection
98/// negotiated as HTTP/1.1 ignores them.
99///
100/// Defaults to no keepalive, no connection age limit, and no stream limit, matching the behavior of a server that only
101/// expects short HTTP/1.1 request/response exchanges. Long-lived HTTP/2 clients -- gRPC clients in particular --
102/// generally want at least a keepalive interval configured so that dead connections are detected rather than
103/// lingering.
104#[derive(Clone, Copy, Debug, Default)]
105pub struct Http2Config {
106    keepalive_interval: Option<Duration>,
107    keepalive_timeout: Option<Duration>,
108    max_connection_age: Option<Duration>,
109    max_connection_age_grace: Option<Duration>,
110    max_concurrent_streams: Option<u32>,
111}
112
113impl Http2Config {
114    /// Creates a configuration carrying the keepalive defaults conventionally used by gRPC servers.
115    ///
116    /// This is a two hour keepalive interval with a twenty second timeout, which is what gRPC implementations generally
117    /// settle on: long enough that idle connections cost almost nothing, short enough that a connection silently
118    /// dropped by a NAT or load balancer is eventually noticed.
119    ///
120    /// Deployments that need dead connections reclaimed faster should configure their own interval via
121    /// [`with_keepalive`][Self::with_keepalive] rather than starting from this.
122    pub fn grpc_defaults() -> Self {
123        Self::default().with_keepalive(DEFAULT_GRPC_KEEPALIVE_INTERVAL, DEFAULT_GRPC_KEEPALIVE_TIMEOUT)
124    }
125
126    /// Sets the HTTP/2 keepalive parameters.
127    ///
128    /// After a connection has been idle for `interval`, the server sends a keepalive PING frame. If no PONG arrives
129    /// within `timeout`, the connection is closed.
130    ///
131    /// Defaults to keepalive being disabled. Shorter intervals detect dead peers faster at the cost of more PING
132    /// traffic on otherwise idle connections; the right value depends on how many idle connections a deployment carries
133    /// and how quickly it needs to reclaim them.
134    pub fn with_keepalive(mut self, interval: Duration, timeout: Duration) -> Self {
135        self.keepalive_interval = Some(interval);
136        self.keepalive_timeout = Some(timeout);
137        self
138    }
139
140    /// Sets the maximum age of a connection, and the grace period that follows it.
141    ///
142    /// Once a connection has existed for `max_age`, the server sends GOAWAY so the peer stops issuing new requests, and
143    /// lets in-flight requests finish. If `grace` is `Some`, the connection is forcibly closed once that period
144    /// elapses; if it is `None`, the server waits for the connection to close on its own.
145    ///
146    /// Defaults to no limit, meaning connections live until either side closes them. Setting a limit is how a
147    /// deployment behind a load balancer spreads load back out periodically, since HTTP/2 clients otherwise pin
148    /// themselves to whichever backend they first reached.
149    pub fn with_max_connection_age(mut self, max_age: Duration, grace: Option<Duration>) -> Self {
150        self.max_connection_age = Some(max_age);
151        self.max_connection_age_grace = grace;
152        self
153    }
154
155    /// Sets the maximum number of concurrent streams allowed on a single connection.
156    ///
157    /// This is sent to the peer as the `SETTINGS_MAX_CONCURRENT_STREAMS` HTTP/2 setting, which bounds how many
158    /// requests a client may have in flight on one connection: a client that reaches the limit waits for a stream to
159    /// complete before opening another.
160    ///
161    /// Defaults to no limit, meaning a client is bounded only by what the connection can carry. Setting a limit caps
162    /// the work a single connection can queue up, at the cost of a client having to open more connections -- or wait
163    /// -- to exceed it.
164    pub fn with_max_concurrent_streams(mut self, max_concurrent_streams: u32) -> Self {
165        self.max_concurrent_streams = Some(max_concurrent_streams);
166        self
167    }
168}
169
170/// An HTTP server.
171///
172/// Serves a set of routes (HTTP or gRPC) over a connection-oriented listener, optionally with TLS.
173///
174/// # Routes
175///
176/// Routes are accumulated on the server itself: HTTP routes via [`add_routes`][Self::add_routes], gRPC services via
177/// [`add_grpc_service`][Self::add_grpc_service]. Both can be called as many times as needed, and both feed the same
178/// router, because a gRPC service is a route set like any other -- one whose paths follow the gRPC naming convention.
179/// The final router is built once, when the server is converted into a supervisor.
180///
181/// The server will respond accordingly depending on whether or not at least one gRPC service was configured. For
182/// example, when an unknown gRPC service/operation is called, it will receive a gRPC-specific response indicating as
183/// such, rather than a generic HTTP "404 Not Found" response.
184///
185/// A caller that has already built the exact router it wants can hand it over with [`with_routes`][Self::with_routes],
186/// which bypasses all of the above.
187///
188/// # Supervision
189///
190/// The server must be run under supervision, and can be converted to a [`Supervisor`] to do so. A number of child workers
191/// handle various aspects of the server and connection lifecycle:
192///
193/// - A dedicated worker accepts new connections from the configured listener.
194/// - For TLS-enabled servers, an interstitial worker is spawned to handle the initial TLS handshake, which helps avoid
195///   head-of-line blocking when accepting subsequent connections due to the amount of time it can take to perform the TLS
196///   handshake.
197/// - Connections are driven on their own worker for isolation. - Background tasks (related to HTTP/2) may also be spawned
198///   as individual workers.
199///
200/// # Shutdown
201///
202/// The subtree carries its own shutdown budget, because a nested supervisor is deliberately exempt from its parent's.
203/// See [`with_graceful_shutdown_timeout`][Self::with_graceful_shutdown_timeout] for what sets it and what the default
204/// is.
205///
206/// # Assertions
207///
208/// `HttpServer` can optionally assert particular information at runtime when the server identifier is set (see
209/// [`with_server_id`][Self::with_server_id]):
210///
211/// - the bound listen address (`BoundListenAddress`, with an identifier of `http-server-<server ID>`)
212pub struct HttpServer {
213    listen_address: ListenAddress,
214    tls_config: Option<ServerConfig>,
215    http2_config: Http2Config,
216    http2_only: bool,
217    graceful_shutdown_timeout: Option<Duration>,
218    server_id: Option<MetaString>,
219    name: MetaString,
220    http_routes: Router,
221    grpc_routes: Option<Routes>,
222    router_override: Option<Router>,
223    worker_pool: Option<Handle>,
224}
225
226impl HttpServer {
227    /// Creates a server that will listen on the given address, with no routes attached.
228    pub fn from_listen_address(listen_address: ListenAddress) -> Self {
229        Self {
230            listen_address,
231            tls_config: None,
232            http2_config: Http2Config::default(),
233            http2_only: false,
234            graceful_shutdown_timeout: None,
235            server_id: None,
236            name: MetaString::from_static(DEFAULT_SERVER_NAME),
237            http_routes: Router::new(),
238            grpc_routes: None,
239            router_override: None,
240            worker_pool: None,
241        }
242    }
243
244    /// Adds HTTP routes to this server.
245    ///
246    /// Can be called more than once, in which case the route sets are merged.
247    ///
248    /// # Panics
249    ///
250    /// Panics if `routes` defines the same path as an existing route on this server.
251    pub fn add_routes(mut self, routes: Router) -> Self {
252        self.http_routes = self.http_routes.merge(routes);
253        self
254    }
255
256    /// Adds a gRPC service to this server.
257    ///
258    /// The service's routes are served from the same listener, and alongside the same HTTP routes, as everything else
259    /// attached to this server.
260    ///
261    /// Can be called more than once to attach several services.
262    pub fn add_grpc_service<S>(mut self, service: S) -> Self
263    where
264        S: Service<Request<GrpcBody>, Error = Infallible> + NamedService + Clone + Send + Sync + 'static,
265        S::Response: IntoResponse,
266        S::Future: Send + 'static,
267    {
268        self.grpc_routes = Some(self.grpc_routes.take().unwrap_or_default().add_service(service));
269        self
270    }
271
272    /// Serves the given router, ignoring any routes otherwise attached to this server.
273    ///
274    /// Any existing routes, whether HTTP or gRPC, will be ignored entirely.
275    pub fn with_routes(mut self, routes: Router) -> Self {
276        self.router_override = Some(routes);
277        self
278    }
279
280    /// Builds the router this server will serve.
281    fn build_router(&self) -> Router {
282        if let Some(router) = &self.router_override {
283            return router.clone();
284        }
285
286        let mut router = self.http_routes.clone();
287        if let Some(grpc_routes) = self.grpc_routes.clone() {
288            router = merge_grpc_routes(router, grpc_routes);
289        }
290
291        router
292    }
293
294    /// Sets the HTTP/2 settings for the server.
295    ///
296    /// Defaults to [`Http2Config::default()`], which enables neither keepalive nor a connection age limit.
297    pub fn with_http2_config(mut self, config: Http2Config) -> Self {
298        self.http2_config = config;
299        self
300    }
301
302    /// Restricts the server to HTTP/2.
303    ///
304    /// By default, the protocol is detected per connection: a client that opens with the HTTP/2 connection preface is
305    /// served over HTTP/2, and anything else is served over HTTP/1.1. Restricting the server to HTTP/2 skips that
306    /// detection, so an HTTP/1.1 client is rejected at the protocol level rather than being routed and answered.
307    ///
308    /// This is worth setting on an endpoint that only ever serves gRPC, where an HTTP/1.1 request is a client error
309    /// worth surfacing as one. Leave it off for any endpoint that also serves REST-ful routes.
310    ///
311    /// Defaults to accepting both HTTP/1.1 and HTTP/2.
312    pub fn with_http2_only(mut self) -> Self {
313        self.http2_only = true;
314        self
315    }
316
317    /// Sets the server identifier to use when asserting any facts for this server.
318    ///
319    /// The identifier also distinguishes this server from any other running under the same supervisor: it becomes part
320    /// of the name the subtree reports, so that logs and per-worker task metrics can be attributed to a specific
321    /// endpoint. Set it on any server that shares a supervisor with another, even when nothing consumes the
322    /// assertions.
323    ///
324    /// If no identifier is set, no assertions will be made at runtime, and the subtree reports a bare
325    /// `http_server`.
326    pub fn with_server_id(mut self, id: impl Into<MetaString>) -> Self {
327        let id = id.into();
328
329        self.name = MetaString::from(format!("{}_{}", DEFAULT_SERVER_NAME, id));
330        self.server_id = Some(id);
331        self
332    }
333
334    /// Sets how long an individual connection is given to drain during shutdown.
335    ///
336    /// When shutdown is signalled, a connection stops accepting new requests and finishes what is already in flight.
337    /// This bounds that: a connection that hasn't finished by then gives up and closes, logging a warning, so one
338    /// wedged peer can't hold the subtree open.
339    ///
340    /// It also sets the subtree's shutdown budget, to this value plus a small amount of slack. That matters because a
341    /// nested supervisor is exempt from its parent's budget, so without one of its own nothing would bound the subtree
342    /// at all. Connections bound themselves, so the budget only comes into play for one that ignores its own deadline.
343    ///
344    /// Defaults to 30 seconds, matching the default shutdown timeout a topology gives its components. Lower it for an
345    /// endpoint that should be abandoned quickly; raise it for one serving long-running requests that are worth
346    /// waiting for.
347    pub fn with_graceful_shutdown_timeout(mut self, timeout: Duration) -> Self {
348        self.graceful_shutdown_timeout = Some(timeout);
349        self
350    }
351
352    /// Sets the TLS configuration for the server.
353    ///
354    /// This enables TLS, after which the server only accepts connections that are encrypted with TLS.
355    ///
356    /// Defaults to TLS being disabled.
357    pub fn with_tls_config(mut self, config: ServerConfig) -> Self {
358        self.tls_config = Some(config);
359        self
360    }
361
362    /// Runs this server's tasks on the given runtime.
363    ///
364    /// Every task the server runs is placed here: accepting connections, TLS handshakes, serving connections, and the
365    /// futures `hyper` hands to the server's executor. Only the subtree's own supervisor loop stays on the runtime it
366    /// was spawned on, since that is where children are registered rather than run.
367    ///
368    /// Use this to keep the server off the runtime that its owner runs on -- a topology component's server belongs on
369    /// the shared worker pool rather than on the runtime driving the topology, since request handling and TLS
370    /// handshake crypto are both compute-heavy enough to add scheduling latency to everything else there.
371    ///
372    /// Defaults to running on whichever runtime the subtree was spawned on.
373    pub fn with_worker_pool(mut self, handle: Handle) -> Self {
374        self.worker_pool = Some(handle);
375        self
376    }
377
378    fn get_server_id(&self) -> Option<Identifier> {
379        self.server_id.clone().map(|sid| get_bound_address_id(&sid))
380    }
381
382    /// Converts this server into a supervisor.
383    ///
384    /// The supervisor is configured to drive an accept loop on the configured listen address, and any resulting
385    /// connections will be spawned and handled on the supervisor.
386    pub fn into_supervisor(self) -> Supervisor {
387        let service = self.build_router();
388        let bound_address_id = self.get_server_id();
389
390        let drain_deadline = self.graceful_shutdown_timeout.unwrap_or(DEFAULT_DRAIN_DEADLINE);
391        let mut supervisor = Supervisor::new(&*self.name)
392            .expect("server name is derived from a non-empty constant")
393            .with_shutdown_budget(drain_deadline.saturating_add(SHUTDOWN_BUDGET_SLACK));
394
395        let acceptor = Acceptor {
396            listen_address: self.listen_address,
397            bound_address_id,
398            tls_config: self.tls_config,
399            http2_config: self.http2_config,
400            http2_only: self.http2_only,
401            drain_deadline,
402            service,
403            supervisor: supervisor.handle(),
404            worker_pool: self.worker_pool.clone(),
405        };
406
407        // The acceptor is placed the same way every other child is, so a configured worker pool covers the whole
408        // server rather than just the work it spawns. Keeping the socket's accept and its serving on one runtime is
409        // also what avoids polling it from a runtime other than the one its readiness is registered with.
410        supervisor.add_worker(place_on_pool(runtime::supervisable(acceptor), self.worker_pool.as_ref()).build());
411
412        supervisor
413    }
414}
415
416/// Applies the configured worker pool, if any, to a child builder.
417fn place_on_pool<'a, S: BuilderState>(
418    builder: ChildBuilder<'a, S>, worker_pool: Option<&Handle>,
419) -> ChildBuilder<'a, S> {
420    match worker_pool {
421        Some(pool) => builder.on_runtime(pool.clone()),
422        None => builder,
423    }
424}
425
426/// Accepts connections for an [`HttpServer`], handing each one off to a child of its own.
427struct Acceptor {
428    listen_address: ListenAddress,
429    bound_address_id: Option<Identifier>,
430    tls_config: Option<ServerConfig>,
431    http2_config: Http2Config,
432    http2_only: bool,
433    drain_deadline: Duration,
434    service: Router,
435    supervisor: SupervisorHandle,
436    worker_pool: Option<Handle>,
437}
438
439#[async_trait]
440impl Supervisable for Acceptor {
441    fn name(&self) -> &str {
442        ACCEPTOR_TASK_NAME
443    }
444
445    async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
446        // Try binding our listener during initialization to surface issues earlier.
447        let listener = ConnectionOrientedListener::from_listen_address(self.listen_address.clone())
448            .await
449            .with_error_context(|| format!("Failed to bind listener for HTTP server ({}).", self.listen_address))?;
450
451        // Assert our bound listen address if we have a configured server ID.
452        if let Some(bound_address_id) = self.bound_address_id.clone() {
453            let dataspace = DataspaceRegistry::try_current()
454                .ok_or_else(|| saluki_error::generic_error!("Dataspace not available for HTTP server."))?;
455
456            dataspace.assert(listener.bound_listen_address(), bound_address_id);
457        }
458
459        let conn_builder = build_conn_builder(
460            SupervisedExecutor::new(self.supervisor.clone(), self.worker_pool.clone()),
461            self.http2_config,
462            self.http2_only,
463        );
464
465        // Resolve the TLS configuration here rather than in the accept loop: an ALPN mismatch or a non-compliant
466        // cipher suite is a configuration error, and surfacing it as an initialization failure keeps it from being
467        // retried as though it were transient.
468        let maybe_tls_acceptor = match self.tls_config.clone() {
469            Some(mut config) => {
470                config.alpn_protocols = alpn_protocols(&conn_builder);
471                ensure_server_config_fips_compliant(&mut config)?;
472
473                Some(TlsAcceptor::from(Arc::new(config)))
474            }
475            None => None,
476        };
477
478        let context = ConnectionContext {
479            conn_builder,
480            service: self.service.clone(),
481            listen_addr: listener.listen_address().clone(),
482            drain_deadline: self.drain_deadline,
483            http2_config: self.http2_config,
484            supervisor: self.supervisor.clone(),
485            worker_pool: self.worker_pool.clone(),
486        };
487
488        Ok(Box::pin(run_accept_loop(
489            listener,
490            context,
491            maybe_tls_acceptor,
492            process_shutdown,
493        )))
494    }
495}
496
497/// Everything needed to hand a freshly accepted connection off to a child of its own.
498#[derive(Clone)]
499struct ConnectionContext {
500    conn_builder: ConnBuilder,
501    service: Router,
502    listen_addr: ListenAddress,
503    drain_deadline: Duration,
504    http2_config: Http2Config,
505    supervisor: SupervisorHandle,
506    worker_pool: Option<Handle>,
507}
508
509impl ConnectionContext {
510    /// Spawns the child that serves `io`.
511    fn spawn_connection<I>(&self, io: I)
512    where
513        I: Read + Write + Unpin + Send + 'static,
514    {
515        let connection = HttpConnection {
516            conn_builder: self.conn_builder.clone(),
517            io: Mutex::new(Some(io)),
518            service: self.service.clone(),
519            listen_addr: self.listen_addr.clone(),
520            drain_deadline: self.drain_deadline,
521            http2_config: self.http2_config,
522        };
523
524        let builder = self.supervisor.supervisable(connection).temporary();
525        place_on_pool(builder, self.worker_pool.as_ref())
526            .with_budget_bounded_shutdown()
527            .spawn();
528    }
529
530    /// Spawns the child that performs the TLS handshake for `stream`, and serves it once it completes.
531    fn spawn_handshake(&self, acceptor: TlsAcceptor, stream: Connection) {
532        let handshake = TlsHandshake {
533            acceptor,
534            stream: Mutex::new(Some(stream)),
535            context: self.clone(),
536        };
537
538        let builder = self.supervisor.supervisable(handshake).temporary();
539        place_on_pool(builder, self.worker_pool.as_ref())
540            .with_budget_bounded_shutdown()
541            .spawn();
542    }
543}
544
545/// Accepts connections until shutdown is signalled or the listener fails.
546///
547/// Every accepted connection becomes a child of the server's supervisor, so this returns as soon as it stops
548/// accepting; waiting for those connections to finish is the supervisor's drain, not this loop's job.
549async fn run_accept_loop(
550    mut listener: ConnectionOrientedListener, context: ConnectionContext, maybe_tls_acceptor: Option<TlsAcceptor>,
551    shutdown: ShutdownHandle,
552) -> Result<(), GenericError> {
553    let tls_enabled = maybe_tls_acceptor.is_some();
554    let listen_addr = context.listen_addr.clone();
555
556    info!(%listen_addr, tls_enabled, "HTTP server started.");
557
558    pin!(shutdown);
559
560    let result = loop {
561        select! {
562            result = listener.accept() => match result {
563                // Neither arm awaits anything: the handshake is a child of its own precisely so that a slow one
564                // can't hold up the connections queued behind it on the listener.
565                Ok(stream) => match &maybe_tls_acceptor {
566                    Some(acceptor) => context.spawn_handshake(acceptor.clone(), stream),
567                    None => context.spawn_connection(TokioIo::new(stream)),
568                },
569                Err(e) => break Err(GenericError::from(e)),
570            },
571
572            _ = &mut shutdown => {
573                debug!(%listen_addr, "Received shutdown signal.");
574                break Ok(());
575            }
576        }
577    };
578
579    info!(%listen_addr, "HTTP server stopped accepting connections.");
580
581    result
582}
583
584/// Performs the TLS handshake for a single accepted connection.
585///
586/// A handshake is abandoned if it doesn't complete within [`TLS_HANDSHAKE_TIMEOUT`], or as soon as the subtree starts
587/// shutting down. Neither case has anything to preserve -- no connection exists yet -- and both are what stop a peer
588/// that connects and then stalls from accumulating, or from holding the drain open.
589struct TlsHandshake {
590    acceptor: TlsAcceptor,
591    stream: Mutex<Option<Connection>>,
592    context: ConnectionContext,
593}
594
595#[async_trait]
596impl Supervisable for TlsHandshake {
597    fn name(&self) -> &str {
598        TLS_HANDSHAKE_TASK_NAME
599    }
600
601    async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
602        let stream = take_once(&self.stream, "TLS handshake")?;
603        let acceptor = self.acceptor.clone();
604        let context = self.context.clone();
605
606        Ok(Box::pin(async move {
607            let listen_addr = context.listen_addr.clone();
608
609            select! {
610                result = timeout(TLS_HANDSHAKE_TIMEOUT, acceptor.accept(stream)) => match result {
611                    Ok(Ok(stream)) => context.spawn_connection(TokioIo::new(stream)),
612                    Ok(Err(e)) => error!(%listen_addr, error = %e, "Failed to complete TLS handshake."),
613                    Err(_) => warn!(
614                        %listen_addr,
615                        "Abandoning TLS handshake that did not complete within {:?}.", TLS_HANDSHAKE_TIMEOUT
616                    ),
617                },
618
619                _ = process_shutdown => debug!(%listen_addr, "Abandoning in-flight TLS handshake at shutdown."),
620            }
621
622            Ok(())
623        }))
624    }
625}
626
627/// Serves a single connection, finishing what it has started if asked to shut down.
628///
629/// When shutdown is triggered, the connection is gracefully shutdown: new requests aren't allowed, but any pending or
630/// in-flight reads/writes will be completed prior to closing the connection.
631///
632/// A connection that outlives the configured maximum age is retired the same way, independently of server shutdown.
633struct HttpConnection<I> {
634    conn_builder: ConnBuilder,
635    io: Mutex<Option<I>>,
636    service: Router,
637    listen_addr: ListenAddress,
638    drain_deadline: Duration,
639    http2_config: Http2Config,
640}
641
642#[async_trait]
643impl<I> Supervisable for HttpConnection<I>
644where
645    I: Read + Write + Unpin + Send + 'static,
646{
647    fn name(&self) -> &str {
648        CONNECTION_TASK_NAME
649    }
650
651    fn shutdown_strategy(&self) -> ShutdownStrategy {
652        // Spawned with a budget-bounded shutdown, so this is only consulted if the subtree somehow has no budget. A
653        // connection bounds its own drain either way, so there is nothing shorter worth imposing here.
654        ShutdownStrategy::Graceful(Duration::MAX)
655    }
656
657    async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
658        let io = take_once(&self.io, "HTTP connection")?;
659        let conn_builder = self.conn_builder.clone();
660        let service = self.service.clone();
661        let listen_addr = self.listen_addr.clone();
662        let drain_deadline = self.drain_deadline;
663        let http2_config = self.http2_config;
664
665        Ok(Box::pin(async move {
666            drive_connection(
667                conn_builder,
668                io,
669                service,
670                listen_addr,
671                process_shutdown,
672                drain_deadline,
673                http2_config,
674            )
675            .await;
676
677            Ok(())
678        }))
679    }
680}
681
682/// Takes the single-use value out of a worker, failing initialization if it has already been taken.
683///
684/// A connection and a handshake are both built around a value that can't be recreated -- a socket -- so they run
685/// exactly once. Their restart policy says as much, and this is the backstop if that ever stops being true.
686fn take_once<T>(slot: &Mutex<Option<T>>, what: &str) -> Result<T, InitializationError> {
687    slot.lock()
688        .expect("single-use worker mutex poisoned")
689        .take()
690        .ok_or_else(|| {
691            InitializationError::from(saluki_error::generic_error!("{} can only be initialized once.", what))
692        })
693}
694
695async fn drive_connection<I>(
696    conn_builder: ConnBuilder, io: I, service: Router, listen_addr: ListenAddress, shutdown: ShutdownHandle,
697    drain_deadline: Duration, http2_config: Http2Config,
698) where
699    I: Read + Write + Unpin + Send + 'static,
700{
701    let service = TowerToHyperService::new(service);
702    let conn = conn_builder.serve_connection(io, service);
703    let mut connection_age = ConnectionAge::new(&http2_config);
704    pin!(conn, shutdown);
705
706    loop {
707        select! {
708            result = conn.as_mut() => {
709                if let Err(e) = result {
710                    error!(%listen_addr, error = %e, "Failed to serve HTTP connection.");
711                }
712
713                return;
714            },
715
716            action = connection_age.next_action() => match action {
717                ConnectionAgeAction::Retire => {
718                    debug!(%listen_addr, "Retiring HTTP connection that reached its maximum age.");
719
720                    conn.as_mut().graceful_shutdown();
721                },
722                ConnectionAgeAction::Close => {
723                    warn!(%listen_addr, "Forcibly closing HTTP connection that did not retire within its grace period.");
724
725                    return;
726                },
727            },
728
729            _ = &mut shutdown => {
730                debug!(%listen_addr, "Draining HTTP connection.");
731
732                conn.as_mut().graceful_shutdown();
733
734                match timeout(drain_deadline, conn.as_mut()).await {
735                    Ok(Ok(())) => {},
736                    Ok(Err(e)) => warn!(%listen_addr, error = %e, "Failed to drain HTTP connection."),
737                    Err(_) => warn!(%listen_addr, "Failed to gracefully drain HTTP connection after {:?}.", drain_deadline)
738                }
739
740                return;
741            },
742        }
743    }
744}
745
746/// An executor that runs `hyper`'s work as supervised children.
747///
748/// `hyper` needs somewhere to put the futures it can't drive from the connection itself. For HTTP/2 that is one future
749/// per stream -- so one per request -- which is why this exists rather than a bare `tokio::spawn`: it is the
750/// difference between per-request work being part of the process tree and being invisible to it.
751///
752/// Children are spawned against a concrete handle rather than the ambient supervisor, so this works wherever `hyper`
753/// happens to call it from.
754#[derive(Clone)]
755struct SupervisedExecutor {
756    supervisor: SupervisorHandle,
757    worker_pool: Option<Handle>,
758}
759
760impl SupervisedExecutor {
761    fn new(supervisor: SupervisorHandle, worker_pool: Option<Handle>) -> Self {
762        Self {
763            supervisor,
764            worker_pool,
765        }
766    }
767}
768
769impl<F> hyper::rt::Executor<F> for SupervisedExecutor
770where
771    F: Future<Output = ()> + Send + 'static,
772{
773    fn execute(&self, fut: F) {
774        let builder = self.supervisor.worker(CONNECTION_BG_TASK_NAME, fut);
775        place_on_pool(builder, self.worker_pool.as_ref()).spawn();
776    }
777}
778
779/// What to do with a connection that has reached an age-based deadline.
780enum ConnectionAgeAction {
781    /// Stop accepting new requests on the connection, and let in-flight ones finish.
782    Retire,
783
784    /// Close the connection, abandoning anything still in flight.
785    Close,
786}
787
788/// Where a connection sits relative to its age-based deadlines.
789enum ConnectionAgePhase {
790    /// Waiting for the maximum age to elapse.
791    Aging(Pin<Box<Sleep>>),
792
793    /// Retired, and waiting for the grace period to elapse before being closed.
794    Grace(Pin<Box<Sleep>>),
795
796    /// No deadline left to wait on, either because none was configured or because all of them have passed.
797    Expired,
798}
799
800/// Tracks the age-based deadlines of a single connection.
801///
802/// Yields at most two actions over the life of a connection -- [`Retire`][ConnectionAgeAction::Retire] once the maximum
803/// age elapses, then [`Close`][ConnectionAgeAction::Close] once the grace period does -- and never resolves again
804/// afterwards, so it is safe to keep selecting on in a loop.
805struct ConnectionAge {
806    phase: ConnectionAgePhase,
807    grace: Option<Duration>,
808}
809
810impl ConnectionAge {
811    fn new(http2_config: &Http2Config) -> Self {
812        Self {
813            phase: match http2_config.max_connection_age {
814                Some(max_age) => ConnectionAgePhase::Aging(Box::pin(sleep(max_age))),
815                None => ConnectionAgePhase::Expired,
816            },
817            grace: http2_config.max_connection_age_grace,
818        }
819    }
820
821    async fn next_action(&mut self) -> ConnectionAgeAction {
822        match &mut self.phase {
823            ConnectionAgePhase::Aging(deadline) => {
824                deadline.as_mut().await;
825
826                // Retiring the connection is the last thing we do to it unless a grace period was configured, in which
827                // case we come back around and close it out if it hasn't finished by then.
828                self.phase = match self.grace {
829                    Some(grace) => ConnectionAgePhase::Grace(Box::pin(sleep(grace))),
830                    None => ConnectionAgePhase::Expired,
831                };
832
833                ConnectionAgeAction::Retire
834            }
835
836            ConnectionAgePhase::Grace(deadline) => {
837                deadline.as_mut().await;
838                self.phase = ConnectionAgePhase::Expired;
839
840                ConnectionAgeAction::Close
841            }
842
843            ConnectionAgePhase::Expired => std::future::pending().await,
844        }
845    }
846}
847
848/// A Tower service converted into a Hyper service.
849#[derive(Debug, Copy, Clone)]
850struct TowerToHyperService<S> {
851    service: S,
852}
853
854impl<S> TowerToHyperService<S> {
855    fn new(tower_service: S) -> Self {
856        Self { service: tower_service }
857    }
858}
859
860impl<S, R> hyper::service::Service<R> for TowerToHyperService<S>
861where
862    S: tower::Service<R> + Clone,
863{
864    type Response = S::Response;
865    type Error = S::Error;
866    type Future = TowerToHyperServiceFuture<S, R>;
867
868    fn call(&self, req: R) -> Self::Future {
869        TowerToHyperServiceFuture {
870            future: self.service.clone().oneshot(req),
871        }
872    }
873}
874
875pin_project! {
876    /// Response future for [`TowerToHyperService`].
877    struct TowerToHyperServiceFuture<S, R>
878    where
879        S: tower::Service<R>,
880    {
881        #[pin]
882        future: Oneshot<S, R>,
883    }
884}
885
886impl<S, R> Future for TowerToHyperServiceFuture<S, R>
887where
888    S: tower::Service<R>,
889{
890    type Output = Result<S::Response, S::Error>;
891
892    #[inline]
893    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
894        self.project().future.poll(cx)
895    }
896}
897
898fn build_conn_builder(executor: SupervisedExecutor, http2_config: Http2Config, http2_only: bool) -> ConnBuilder {
899    let mut builder = Builder::new(executor);
900    builder
901        .http1()
902        .timer(TokioTimer::new())
903        .header_read_timeout(Duration::from_secs(10));
904
905    builder
906        .http2()
907        .timer(TokioTimer::new())
908        .keep_alive_interval(http2_config.keepalive_interval);
909
910    if let Some(keepalive_timeout) = http2_config.keepalive_timeout {
911        builder.http2().keep_alive_timeout(keepalive_timeout);
912    }
913
914    if let Some(max_concurrent_streams) = http2_config.max_concurrent_streams {
915        builder.http2().max_concurrent_streams(max_concurrent_streams);
916    }
917
918    if http2_only {
919        builder = builder.http2_only();
920    }
921
922    builder
923}
924
925/// ALPN protocols a server advertises, in preference order.
926fn alpn_protocols(conn_builder: &ConnBuilder) -> Vec<Vec<u8>> {
927    let mut protocols = vec![];
928
929    if conn_builder.is_http2_available() {
930        protocols.push(b"h2".to_vec());
931    }
932
933    if conn_builder.is_http1_available() {
934        protocols.push(b"http/1.1".to_vec());
935    }
936
937    protocols
938}
939
940fn get_bound_address_id(server_id: &str) -> Identifier {
941    Identifier::from(format!("http-server-{}", server_id))
942}
943
944#[cfg(test)]
945mod tests {
946    use std::net::{SocketAddr, TcpListener as StdTcpListener};
947    use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
948
949    use http::{Response, StatusCode, Version};
950    use http_body_util::{Empty, Full};
951    use hyper_util::client::legacy::Client;
952    use hyper_util::rt::TokioExecutor;
953    use saluki_core::runtime::state::{DataspaceUpdate, IdentifierFilter};
954    use saluki_core::runtime::SupervisorError;
955    use saluki_metrics::test::TestRecorder;
956    use saluki_tls::test_util::SelfSignedCert;
957    use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
958    use tokio::net::TcpStream;
959    use tokio::sync::oneshot;
960    use tokio::task::JoinHandle;
961    use tokio::time::{timeout, Instant};
962    use tower::util::service_fn;
963
964    use super::*;
965    use crate::net::addr::BoundListenAddress;
966    #[cfg(unix)]
967    use crate::net::server::test_util::connect_unix;
968    use crate::net::server::test_util::{connect_tcp, DataspaceCapture, ServerTestHarness};
969
970    /// Bound on any server await in these tests, so a hang fails rather than stalling the suite.
971    const TEST_TIMEOUT: Duration = Duration::from_secs(5);
972
973    /// A running server subtree, together with the trigger that stops it.
974    struct RunningServer {
975        // Not resolved until `bound_tcp_address` is called: a server whose initialization is expected to fail (e.g.
976        // to hit an already-bound port) may tear down before the capture worker ever gets to send anything, and
977        // tests exercising that don't ask for the bound address at all.
978        dataspace_rx: Mutex<Option<oneshot::Receiver<DataspaceRegistry>>>,
979        bound_address_id: Identifier,
980        shutdown_tx: Option<oneshot::Sender<()>>,
981        task: JoinHandle<Result<(), SupervisorError>>,
982    }
983
984    impl RunningServer {
985        /// Starts `server` on its own task.
986        ///
987        /// A server can't be driven by hand any more: its connections are children of its supervisor, so there has to
988        /// be one running for anything to be served at all.
989        async fn start(server: HttpServer) -> Self {
990            // A server ID is what makes the bound address observable through the dataspace, so give it one when the
991            // caller hasn't already set one for their own purposes (e.g. to check a per-server metric tag).
992            static NEXT_ID: AtomicU64 = AtomicU64::new(0);
993            let server_id = server.server_id.clone().unwrap_or_else(|| {
994                MetaString::from(format!("running-server-{}", NEXT_ID.fetch_add(1, Ordering::Relaxed)))
995            });
996            let server = server.with_server_id(server_id.clone());
997
998            let (capture, dataspace_rx) = DataspaceCapture::new();
999            let mut supervisor = server.into_supervisor();
1000            supervisor.add_worker(capture);
1001
1002            let (shutdown_tx, shutdown_rx) = oneshot::channel();
1003            let task = tokio::spawn(async move { supervisor.run_with_shutdown(shutdown_rx).await });
1004
1005            Self {
1006                dataspace_rx: Mutex::new(Some(dataspace_rx)),
1007                bound_address_id: get_bound_address_id(&server_id),
1008                shutdown_tx: Some(shutdown_tx),
1009                task,
1010            }
1011        }
1012
1013        /// Looks up the address the server actually bound, once it has finished initializing.
1014        async fn bound_tcp_address(&self) -> SocketAddr {
1015            let dataspace_rx = self
1016                .dataspace_rx
1017                .lock()
1018                .expect("dataspace receiver lock should not be poisoned")
1019                .take()
1020                .expect("the bound address should only be looked up once");
1021            let dataspace = timeout(TEST_TIMEOUT, dataspace_rx)
1022                .await
1023                .expect("should capture the supervisor dataspace")
1024                .expect("dataspace capture worker should send the registry");
1025
1026            let mut subscription =
1027                dataspace.subscribe::<BoundListenAddress>(IdentifierFilter::exact(self.bound_address_id.clone()));
1028
1029            match timeout(TEST_TIMEOUT, subscription.recv()).await {
1030                Ok(Some(DataspaceUpdate::Asserted(_, BoundListenAddress::Tcp(addr)))) => addr,
1031                update => panic!(
1032                    "expected a bound TCP address assertion for '{:?}', got {update:?}",
1033                    self.bound_address_id
1034                ),
1035            }
1036        }
1037
1038        /// Signals shutdown without waiting for the subtree to finish.
1039        fn signal_shutdown(&mut self) {
1040            let _ = self.shutdown_tx.take().expect("should only shut down once").send(());
1041        }
1042
1043        /// Awaits the subtree, returning whatever it reported.
1044        async fn join(self) -> Result<(), SupervisorError> {
1045            timeout(TEST_TIMEOUT, self.task)
1046                .await
1047                .expect("server should stop before timeout")
1048                .expect("server task should not panic")
1049        }
1050
1051        /// Signals shutdown and asserts the subtree drained cleanly.
1052        async fn shutdown(mut self) {
1053            self.signal_shutdown();
1054            let result = self.join().await;
1055            assert!(result.is_ok(), "server should stop cleanly: {result:?}");
1056        }
1057    }
1058
1059    /// Builds a connection builder for the tests that only inspect its configuration.
1060    fn test_conn_builder(http2_only: bool) -> ConnBuilder {
1061        let supervisor = Supervisor::new("alpn-test").expect("test supervisor name should be valid");
1062
1063        build_conn_builder(
1064            SupervisedExecutor::new(supervisor.handle(), None),
1065            Http2Config::default(),
1066            http2_only,
1067        )
1068    }
1069
1070    /// Reserves a loopback port and releases it, yielding an address a server can bind.
1071    ///
1072    /// Inherently racy against anything else on the host, but the window is small and there is no way to hand an
1073    /// already-bound listener to the supervised server.
1074    fn free_local_addr() -> SocketAddr {
1075        let listener = StdTcpListener::bind("127.0.0.1:0").expect("should bind an ephemeral port");
1076        let addr = listener.local_addr().expect("should have a local address");
1077        drop(listener);
1078        addr
1079    }
1080
1081    /// Builds a server whose handler runs `f` for every request.
1082    fn server_with<F, Fut>(listen_address: ListenAddress, f: F) -> HttpServer
1083    where
1084        F: Fn() -> Fut + Send + Sync + Clone + 'static,
1085        Fut: Future<Output = Result<Response<Full<bytes::Bytes>>, Infallible>> + Send + 'static,
1086    {
1087        HttpServer::from_listen_address(listen_address).add_routes(routes_answering_with(f))
1088    }
1089
1090    /// Builds a router whose every route runs `f`.
1091    fn routes_answering_with<F, Fut>(f: F) -> Router
1092    where
1093        F: Fn() -> Fut + Send + Sync + Clone + 'static,
1094        Fut: Future<Output = Result<Response<Full<bytes::Bytes>>, Infallible>> + Send + 'static,
1095    {
1096        Router::new().fallback_service(service_fn(move |_req: axum::extract::Request| f()))
1097    }
1098
1099    /// A handler that responds immediately.
1100    async fn ok_response() -> Result<Response<Full<bytes::Bytes>>, Infallible> {
1101        Ok(Response::new(Full::new(bytes::Bytes::from_static(b"ok"))))
1102    }
1103
1104    /// Drives a request through a router without going near a socket.
1105    async fn route_request(router: Router, uri: &str, content_type: Option<&str>) -> Response<axum::body::Body> {
1106        let mut builder = Request::builder().uri(uri);
1107        if let Some(content_type) = content_type {
1108            builder = builder.header(http::header::CONTENT_TYPE, content_type);
1109        }
1110
1111        let request = builder
1112            .body(axum::body::Body::empty())
1113            .expect("should build the request");
1114
1115        router.oneshot(request).await.expect("router should answer")
1116    }
1117
1118    /// A gRPC service that answers immediately, for attaching gRPC routes to a server under test.
1119    #[derive(Clone)]
1120    struct EmptyGrpcService;
1121
1122    impl NamedService for EmptyGrpcService {
1123        const NAME: &'static str = "test.EmptyService";
1124    }
1125
1126    impl Service<Request<GrpcBody>> for EmptyGrpcService {
1127        type Response = Response<axum::body::Body>;
1128        type Error = Infallible;
1129        type Future = std::future::Ready<Result<Self::Response, Infallible>>;
1130
1131        fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1132            Poll::Ready(Ok(()))
1133        }
1134
1135        fn call(&mut self, _req: Request<GrpcBody>) -> Self::Future {
1136            std::future::ready(Ok(Response::new(axum::body::Body::empty())))
1137        }
1138    }
1139
1140    #[tokio::test]
1141    async fn accumulated_http_and_grpc_routes_are_served_together() {
1142        let router = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1143            .add_routes(Router::new().route("/first", axum::routing::get(|| async { "first" })))
1144            .add_routes(Router::new().route("/second", axum::routing::get(|| async { "second" })))
1145            .add_grpc_service(EmptyGrpcService)
1146            .build_router();
1147
1148        // Both route sets survive being merged, as do the gRPC service's own routes.
1149        for path in ["/first", "/second"] {
1150            let response = route_request(router.clone(), path, None).await;
1151            assert_eq!(response.status(), StatusCode::OK, "{path} should be served");
1152        }
1153
1154        let grpc_response = route_request(router.clone(), "/test.EmptyService/Method", Some("application/grpc")).await;
1155        assert_eq!(grpc_response.status(), StatusCode::OK);
1156
1157        // Attaching a gRPC service is what brings in the protocol-aware fallback, so an unmatched request is now
1158        // answered in whichever protocol it arrived in.
1159        let unmatched_grpc = route_request(router.clone(), "/test.Missing/Method", Some("application/grpc")).await;
1160        assert_eq!(
1161            unmatched_grpc
1162                .headers()
1163                .get("grpc-status")
1164                .and_then(|v| v.to_str().ok()),
1165            Some("12")
1166        );
1167
1168        let unmatched_http = route_request(router, "/nowhere", None).await;
1169        assert_eq!(unmatched_http.status(), StatusCode::NOT_FOUND);
1170    }
1171
1172    #[test]
1173    fn the_subtree_name_reflects_the_server_id() {
1174        // Two servers sharing a supervisor have to be tellable apart in logs and per-worker task metrics. That is what
1175        // the identifier buys beyond namespacing assertions, and why an endpoint sets one even when nothing consumes
1176        // the assertions it enables. The name reaches those metrics as the subtree's supervisor ID, which every child
1177        // process name is scoped under.
1178        let unnamed = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0)).into_supervisor();
1179        assert_eq!(unnamed.id(), DEFAULT_SERVER_NAME);
1180
1181        let grpc = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1182            .with_server_id("otlp-grpc")
1183            .into_supervisor();
1184        let http = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1185            .with_server_id("otlp-http")
1186            .into_supervisor();
1187        assert_eq!(grpc.id(), "http_server_otlp-grpc");
1188        assert_eq!(http.id(), "http_server_otlp-http");
1189    }
1190
1191    #[tokio::test]
1192    async fn an_http_only_server_keeps_its_own_fallback() {
1193        // With no gRPC service attached there is no protocol-aware fallback to install, so the caller's own fallback
1194        // is left alone rather than being displaced by one it never asked for.
1195        let router = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1196            .add_routes(Router::new().fallback(|| async { StatusCode::IM_A_TEAPOT }))
1197            .build_router();
1198
1199        let response = route_request(router, "/anything", None).await;
1200        assert_eq!(response.status(), StatusCode::IM_A_TEAPOT);
1201    }
1202
1203    #[tokio::test]
1204    async fn overriding_the_router_discards_accumulated_routes() {
1205        let router = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1206            .add_routes(Router::new().route("/added", axum::routing::get(|| async { "added" })))
1207            .add_grpc_service(EmptyGrpcService)
1208            .with_routes(Router::new().route("/override", axum::routing::get(|| async { "override" })))
1209            .build_router();
1210
1211        let overridden = route_request(router.clone(), "/override", None).await;
1212        assert_eq!(overridden.status(), StatusCode::OK);
1213
1214        // Everything that was added beforehand is gone, gRPC routes included.
1215        let added = route_request(router.clone(), "/added", None).await;
1216        assert_eq!(added.status(), StatusCode::NOT_FOUND);
1217
1218        let grpc = route_request(router, "/test.EmptyService/Method", Some("application/grpc")).await;
1219        assert_eq!(grpc.status(), StatusCode::NOT_FOUND);
1220    }
1221
1222    #[tokio::test]
1223    async fn publishes_bound_tcp_address() {
1224        let harness = ServerTestHarness::start("http-tcp-bound-address", |supervisor, server_id| {
1225            let server = server_with(ListenAddress::tcp_loopback(0), ok_response).with_server_id(server_id);
1226            supervisor.add_worker(server.into_supervisor());
1227        })
1228        .await;
1229
1230        let local_tcp_address = match harness.bound_address().await {
1231            BoundListenAddress::Tcp(addr) => addr,
1232            other_addr => panic!("expected TCP address, got {:?}", other_addr),
1233        };
1234
1235        assert_ne!(local_tcp_address.port(), 0);
1236
1237        // Try and connect.
1238        //
1239        // Panics if the connection fails or we timeout trying to connect.. so we don't assert anything
1240        // here since no panic means "it worked."
1241        let stream = connect_tcp(local_tcp_address).await;
1242        drop(stream);
1243
1244        harness.shutdown().await;
1245    }
1246
1247    #[cfg(unix)]
1248    #[tokio::test]
1249    async fn publishes_bound_unix_address() {
1250        let tempdir = tempfile::tempdir().expect("should create temp dir");
1251        let socket_path = tempdir.path().join("http.sock");
1252        let listen_address = ListenAddress::Unix(socket_path.clone());
1253
1254        let harness = ServerTestHarness::start("http-unix-bound-address", move |supervisor, server_id| {
1255            let server = server_with(listen_address, ok_response).with_server_id(server_id);
1256            supervisor.add_worker(server.into_supervisor());
1257        })
1258        .await;
1259
1260        let local_unix_address = match harness.bound_address().await {
1261            BoundListenAddress::Unix(addr) => addr,
1262            other_addr => panic!("expected UDS address, got {:?}", other_addr),
1263        };
1264
1265        assert_eq!(socket_path, local_unix_address);
1266
1267        // Try and connect.
1268        //
1269        // Panics if the connection fails or we timeout trying to connect.. so we don't assert anything
1270        // here since no panic means "it worked."
1271        let stream = connect_unix(&socket_path).await;
1272        drop(stream);
1273
1274        harness.shutdown().await;
1275    }
1276
1277    /// Builds a TLS config for a server presenting `cert`.
1278    fn server_tls_config(cert: &SelfSignedCert) -> ServerConfig {
1279        ServerConfig::builder()
1280            .with_no_client_auth()
1281            .with_single_cert(cert.cert_chain(), cert.private_key())
1282            .expect("should build TLS config")
1283    }
1284
1285    /// Handshakes with a TLS server offering exactly `client_alpn`, returning the negotiated protocol.
1286    async fn negotiate_alpn(
1287        address: SocketAddr, cert_chain: Vec<rustls::pki_types::CertificateDer<'static>>, client_alpn: &[&[u8]],
1288    ) -> std::io::Result<Option<Vec<u8>>> {
1289        let mut roots = rustls::RootCertStore::empty();
1290        for cert in cert_chain {
1291            roots.add(cert).expect("should trust the self-signed cert");
1292        }
1293
1294        let mut client_config = rustls::ClientConfig::builder()
1295            .with_root_certificates(roots)
1296            .with_no_client_auth();
1297        client_config.alpn_protocols = client_alpn.iter().map(|protocol| protocol.to_vec()).collect();
1298
1299        let stream = TcpStream::connect(address).await.expect("should connect");
1300        let server_name = rustls::pki_types::ServerName::try_from("localhost").expect("should be a valid server name");
1301        let tls_stream = tokio_rustls::TlsConnector::from(Arc::new(client_config))
1302            .connect(server_name, stream)
1303            .await?;
1304
1305        Ok(tls_stream.get_ref().1.alpn_protocol().map(ToOwned::to_owned))
1306    }
1307
1308    /// Starts a TLS server on an ephemeral port, returning its address and the cert chain to trust.
1309    async fn start_tls_server(
1310        harness_id: &str, http2_only: bool,
1311    ) -> (
1312        ServerTestHarness,
1313        SocketAddr,
1314        Vec<rustls::pki_types::CertificateDer<'static>>,
1315    ) {
1316        let _ = saluki_tls::initialize_default_crypto_provider();
1317        let cert = SelfSignedCert::localhost();
1318        let cert_chain = cert.cert_chain();
1319        let tls_config = server_tls_config(&cert);
1320
1321        let harness = ServerTestHarness::start(harness_id, move |supervisor, server_id| {
1322            let mut server = server_with(ListenAddress::tcp_loopback(0), ok_response)
1323                .with_tls_config(tls_config)
1324                .with_server_id(server_id);
1325            if http2_only {
1326                server = server.with_http2_only();
1327            }
1328            supervisor.add_worker(server.into_supervisor());
1329        })
1330        .await;
1331
1332        let address = match harness.bound_address().await {
1333            BoundListenAddress::Tcp(addr) => addr,
1334            other_addr => panic!("expected TCP address, got {:?}", other_addr),
1335        };
1336
1337        (harness, address, cert_chain)
1338    }
1339
1340    #[test]
1341    fn advertised_alpn_protocols_track_the_protocol_restriction() {
1342        let conn_builder_both = test_conn_builder(false);
1343        assert_eq!(
1344            alpn_protocols(&conn_builder_both),
1345            vec![b"h2".to_vec(), b"http/1.1".to_vec()]
1346        );
1347
1348        let conn_builder_http2_only = test_conn_builder(true);
1349        assert_eq!(alpn_protocols(&conn_builder_http2_only), vec![b"h2".to_vec()]);
1350    }
1351
1352    #[tokio::test]
1353    async fn tls_server_advertises_both_protocols_by_default() {
1354        let (harness, address, cert_chain) = start_tls_server("http-alpn-auto", false).await;
1355
1356        // A client that prefers HTTP/2 gets it, and one that only speaks HTTP/1.1 is still served.
1357        let negotiated = negotiate_alpn(address, cert_chain.clone(), &[b"h2", b"http/1.1"])
1358            .await
1359            .expect("handshake should succeed");
1360        assert_eq!(negotiated, Some(b"h2".to_vec()));
1361
1362        let negotiated = negotiate_alpn(address, cert_chain, &[b"http/1.1"])
1363            .await
1364            .expect("handshake should succeed");
1365        assert_eq!(negotiated, Some(b"http/1.1".to_vec()));
1366
1367        harness.shutdown().await;
1368    }
1369
1370    #[tokio::test]
1371    async fn tls_server_advertises_only_http2_when_restricted() {
1372        let (harness, address, cert_chain) = start_tls_server("http-alpn-http2-only", true).await;
1373
1374        let negotiated = negotiate_alpn(address, cert_chain.clone(), &[b"h2", b"http/1.1"])
1375            .await
1376            .expect("handshake should succeed");
1377        assert_eq!(negotiated, Some(b"h2".to_vec()));
1378
1379        // The point of the restriction: an HTTP/1.1-only client cannot get a connection at all, instead of negotiating
1380        // a protocol the connection would immediately be torn down for. That the HTTP/2 client above succeeded against
1381        // the same certificate is what makes this failure attributable to ALPN rather than to trust.
1382        //
1383        // Which error the client sees is deliberately not pinned down: `rustls` rejects the handshake with a
1384        // `no_application_protocol` alert, but the accept loop drops the stream on handshake failure, so whether that
1385        // alert reaches the client before EOF is a race.
1386        negotiate_alpn(address, cert_chain, &[b"http/1.1"])
1387            .await
1388            .expect_err("handshake should fail when no offered protocol is served");
1389
1390        harness.shutdown().await;
1391    }
1392
1393    #[tokio::test]
1394    async fn a_stalled_tls_handshake_does_not_block_other_connections() {
1395        // The reason handshakes are children of their own. A peer that connects and then says nothing leaves its
1396        // handshake outstanding indefinitely; when handshakes were awaited inline in the accept loop, that one peer
1397        // was enough to stop the server accepting anything at all, and this second client would never be served.
1398        let (harness, address, cert_chain) = start_tls_server("http-tls-head-of-line", false).await;
1399
1400        let _stalled = TcpStream::connect(address).await.expect("should connect");
1401
1402        let negotiated = timeout(TEST_TIMEOUT, negotiate_alpn(address, cert_chain, &[b"h2"]))
1403            .await
1404            .expect("a stalled handshake must not delay another client's")
1405            .expect("handshake should succeed");
1406        assert_eq!(negotiated, Some(b"h2".to_vec()));
1407
1408        // Shutting down cleanly is the other half: the stalled handshake is abandoned rather than held onto until the
1409        // subtree's budget elapses, which would take far longer than the harness allows.
1410        harness.shutdown().await;
1411    }
1412
1413    /// Records the name of the thread a request handler ran on.
1414    fn thread_recording_server(listen_address: ListenAddress) -> (HttpServer, Arc<Mutex<Option<String>>>) {
1415        let thread_name = Arc::new(Mutex::new(None));
1416        let recorder = Arc::clone(&thread_name);
1417
1418        let server = server_with(listen_address, move || {
1419            let recorder = Arc::clone(&recorder);
1420            async move {
1421                *recorder.lock().unwrap() = Some(std::thread::current().name().unwrap_or_default().to_string());
1422                ok_response().await
1423            }
1424        });
1425
1426        (server, thread_name)
1427    }
1428
1429    /// Builds a runtime whose threads are recognizable by name.
1430    fn named_pool(name: &'static str) -> tokio::runtime::Runtime {
1431        tokio::runtime::Builder::new_multi_thread()
1432            .worker_threads(1)
1433            .thread_name(name)
1434            .enable_all()
1435            .build()
1436            .expect("should build pool")
1437    }
1438
1439    #[tokio::test]
1440    async fn the_worker_pool_runs_request_handling() {
1441        // What `with_worker_pool` is for, and the property the topology components depend on: request handling stays
1442        // off the runtime driving the topology, since decoding a large request is compute-heavy enough to add
1443        // scheduling latency to everything else there. The acceptor, handshakes, connections, and the futures `hyper`
1444        // executes are all placed through the same call, so covering the handler covers the arrangement.
1445        let pool = named_pool("http-pool-test");
1446
1447        let (server, handler_thread) = thread_recording_server(ListenAddress::tcp_loopback(0));
1448        let server = RunningServer::start(server.with_worker_pool(pool.handle().clone())).await;
1449        let addr = server.bound_tcp_address().await;
1450
1451        let client = Client::builder(TokioExecutor::new()).build_http::<Empty<bytes::Bytes>>();
1452        let uri = format!("http://{addr}/").parse().expect("should be a valid URI");
1453        let response = timeout(TEST_TIMEOUT, client.get(uri))
1454            .await
1455            .expect("server should answer")
1456            .expect("request should succeed");
1457        assert_eq!(response.status(), StatusCode::OK);
1458
1459        let handler_thread = handler_thread.lock().unwrap().clone().expect("handler should have run");
1460        assert!(
1461            handler_thread.starts_with("http-pool-test"),
1462            "request handling must run on the worker pool, but ran on thread {handler_thread:?}"
1463        );
1464
1465        server.shutdown().await;
1466        pool.shutdown_background();
1467    }
1468
1469    #[tokio::test]
1470    async fn the_worker_pool_runs_request_handling_behind_tls() {
1471        // The same, over TLS, which is the path that actually goes through a handshake child before a connection child
1472        // exists at all. Serving the request at all means the hand-off between the two survived being placed.
1473        let _ = saluki_tls::initialize_default_crypto_provider();
1474        let cert = SelfSignedCert::localhost();
1475        let tls_config = server_tls_config(&cert);
1476        let pool = named_pool("http-tls-pool-test");
1477
1478        let (server, handler_thread) = thread_recording_server(ListenAddress::tcp_loopback(0));
1479        let server = RunningServer::start(
1480            server
1481                .with_tls_config(tls_config)
1482                .with_worker_pool(pool.handle().clone()),
1483        )
1484        .await;
1485        let addr = server.bound_tcp_address().await;
1486
1487        let negotiated = timeout(TEST_TIMEOUT, negotiate_alpn(addr, cert.cert_chain(), &[b"http/1.1"]))
1488            .await
1489            .expect("handshake should complete")
1490            .expect("handshake should succeed");
1491        assert_eq!(negotiated, Some(b"http/1.1".to_vec()));
1492
1493        // `negotiate_alpn` only handshakes, so drive an actual request through to reach the handler.
1494        let mut roots = rustls::RootCertStore::empty();
1495        for cert in cert.cert_chain() {
1496            roots.add(cert).expect("should trust the self-signed cert");
1497        }
1498        let mut client_config = rustls::ClientConfig::builder()
1499            .with_root_certificates(roots)
1500            .with_no_client_auth();
1501        client_config.alpn_protocols = vec![b"http/1.1".to_vec()];
1502
1503        let stream = TcpStream::connect(addr).await.expect("should connect");
1504        let server_name = rustls::pki_types::ServerName::try_from("localhost").expect("should be a valid server name");
1505        let mut tls_stream = tokio_rustls::TlsConnector::from(Arc::new(client_config))
1506            .connect(server_name, stream)
1507            .await
1508            .expect("handshake should succeed");
1509        tls_stream
1510            .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
1511            .await
1512            .expect("should write request");
1513
1514        let mut response = Vec::new();
1515        timeout(TEST_TIMEOUT, tls_stream.read_to_end(&mut response))
1516            .await
1517            .expect("response should arrive")
1518            .expect("should read response");
1519        assert!(
1520            response.ends_with(b"ok"),
1521            "expected the request to be served, got {:?}",
1522            String::from_utf8_lossy(&response)
1523        );
1524
1525        let handler_thread = handler_thread.lock().unwrap().clone().expect("handler should have run");
1526        assert!(
1527            handler_thread.starts_with("http-tls-pool-test"),
1528            "request handling must run on the worker pool, but ran on thread {handler_thread:?}"
1529        );
1530
1531        server.shutdown().await;
1532        pool.shutdown_background();
1533    }
1534
1535    #[tokio::test]
1536    async fn publishes_bound_tcp_address_with_tls() {
1537        let _ = saluki_tls::initialize_default_crypto_provider();
1538        let cert = SelfSignedCert::localhost();
1539        let tls_config = ServerConfig::builder()
1540            .with_no_client_auth()
1541            .with_single_cert(cert.cert_chain(), cert.private_key())
1542            .expect("should build TLS config");
1543
1544        let harness = ServerTestHarness::start("http-tcp-tls-bound-address", move |supervisor, server_id| {
1545            let server = server_with(ListenAddress::tcp_loopback(0), ok_response)
1546                .with_tls_config(tls_config)
1547                .with_server_id(server_id);
1548            supervisor.add_worker(server.into_supervisor());
1549        })
1550        .await;
1551
1552        let local_tcp_address = match harness.bound_address().await {
1553            BoundListenAddress::Tcp(addr) => addr,
1554            other_addr => panic!("expected TCP address, got {:?}", other_addr),
1555        };
1556
1557        assert_ne!(local_tcp_address.port(), 0);
1558
1559        // Try and connect.
1560        //
1561        // Panics if the connection fails or we timeout trying to connect.. so we don't assert anything
1562        // here since no panic means "it worked."
1563        let stream = connect_tcp(local_tcp_address).await;
1564        drop(stream);
1565
1566        harness.shutdown().await;
1567    }
1568
1569    #[cfg(unix)]
1570    #[tokio::test]
1571    async fn publishes_bound_unix_address_with_tls() {
1572        let _ = saluki_tls::initialize_default_crypto_provider();
1573        let cert = SelfSignedCert::localhost();
1574        let tls_config = ServerConfig::builder()
1575            .with_no_client_auth()
1576            .with_single_cert(cert.cert_chain(), cert.private_key())
1577            .expect("should build TLS config");
1578
1579        let tempdir = tempfile::tempdir().expect("should create temp dir");
1580        let socket_path = tempdir.path().join("http.sock");
1581        let listen_address = ListenAddress::Unix(socket_path.clone());
1582
1583        let harness = ServerTestHarness::start("http-unix-tls-bound-address", move |supervisor, server_id| {
1584            let server = server_with(listen_address, ok_response)
1585                .with_tls_config(tls_config)
1586                .with_server_id(server_id);
1587            supervisor.add_worker(server.into_supervisor());
1588        })
1589        .await;
1590
1591        let local_unix_address = match harness.bound_address().await {
1592            BoundListenAddress::Unix(addr) => addr,
1593            other_addr => panic!("expected UDS address, got {:?}", other_addr),
1594        };
1595
1596        assert_eq!(socket_path, local_unix_address);
1597
1598        // Try and connect.
1599        //
1600        // Panics if the connection fails or we timeout trying to connect.. so we don't assert anything
1601        // here since no panic means "it worked."
1602        let stream = connect_unix(&socket_path).await;
1603        drop(stream);
1604
1605        harness.shutdown().await;
1606    }
1607
1608    #[tokio::test]
1609    async fn binds_its_listener_during_initialization() {
1610        // The listener is bound by the acceptor's `initialize`, not by its run future, which is what makes a bind
1611        // failure a non-restartable initialization error rather than something retried forever. Observed here by
1612        // connecting: probing with a bind of our own would race the server for the address and could win.
1613        let server = RunningServer::start(server_with(ListenAddress::tcp_loopback(0), ok_response)).await;
1614
1615        server.bound_tcp_address().await;
1616
1617        server.shutdown().await;
1618    }
1619
1620    #[tokio::test]
1621    async fn bind_failure_is_an_initialization_error() {
1622        // Hold the address so the server can't have it. An initialization error is non-restartable, which is the point:
1623        // an unusable listen address should fail the subtree rather than being retried forever. That it propagates as
1624        // `FailedToInitialize` -- rather than as a runtime error the parent would restart -- is what carries that
1625        // property across the subtree boundary.
1626        let addr = free_local_addr();
1627        let _held = StdTcpListener::bind(addr).expect("should hold the address");
1628
1629        let server = RunningServer::start(server_with(ListenAddress::Tcp(addr), ok_response)).await;
1630
1631        // Returning at all (before the timeout in `join`) is half the assertion: a retry loop would never get here.
1632        match server.join().await {
1633            Ok(()) => panic!("the subtree should have failed to bind {addr}"),
1634            Err(SupervisorError::FailedToInitialize { source, .. }) => {
1635                let error = source.to_string();
1636                assert!(error.contains("Failed to bind listener"), "unexpected error: {error}");
1637            }
1638            Err(e) => panic!("expected an initialization failure, got {e:?}"),
1639        }
1640    }
1641
1642    #[tokio::test]
1643    async fn releases_its_port_once_the_subtree_finishes() {
1644        // The whole reason for supervising the server: when its subtree stops, the socket is gone. Previously the
1645        // acceptor was a detached task that outlived whatever spawned it.
1646        let server = RunningServer::start(server_with(ListenAddress::tcp_loopback(0), ok_response)).await;
1647        let addr = server.bound_tcp_address().await;
1648
1649        server.shutdown().await;
1650
1651        assert!(
1652            StdTcpListener::bind(addr).is_ok(),
1653            "the server should have released {addr} when its subtree finished"
1654        );
1655    }
1656
1657    #[tokio::test]
1658    async fn a_half_sent_request_does_not_wedge_the_drain() {
1659        // A peer that writes a partial request head and stalls keeps its connection permanently non-idle, so
1660        // `graceful_shutdown` alone never closes it. Before the connection builder had a timer and the drain had a
1661        // deadline, one such socket stalled shutdown indefinitely -- for the OTLP receivers that meant every ADP
1662        // shutdown hanging until the component budget forced an abort.
1663        //
1664        // A clean result is the assertion that matters: the connection bounds its own drain and exits, rather than
1665        // being force-aborted by the subtree's budget, which would surface as `ShutdownTimedOut` all the way up.
1666        let server = RunningServer::start(
1667            server_with(ListenAddress::tcp_loopback(0), ok_response)
1668                .with_graceful_shutdown_timeout(Duration::from_secs(1)),
1669        )
1670        .await;
1671        let addr = server.bound_tcp_address().await;
1672
1673        let mut stream = TcpStream::connect(addr).await.expect("should connect");
1674        stream
1675            .write_all(b"GET / HTTP/1.1\r\nHost: localhost")
1676            .await
1677            .expect("should write a partial request head");
1678        stream.flush().await.expect("should flush");
1679
1680        // Let the server read what there is before signalling, so the connection is genuinely mid-parse.
1681        tokio::time::sleep(Duration::from_millis(100)).await;
1682
1683        server.shutdown().await;
1684    }
1685
1686    #[tokio::test]
1687    async fn does_not_finish_until_in_flight_requests_do() {
1688        // Shutdown stops the server accepting, but a request already being served has to complete first. The
1689        // connection is a supervised child, so the subtree's drain is what waits for it; without that the subtree
1690        // would return as soon as the acceptor stopped and the response would be lost.
1691        let handler_started = Arc::new(AtomicBool::new(false));
1692        let started = Arc::clone(&handler_started);
1693        let mut server = RunningServer::start(server_with(ListenAddress::tcp_loopback(0), move || {
1694            let started = Arc::clone(&started);
1695            async move {
1696                started.store(true, Ordering::SeqCst);
1697                tokio::time::sleep(Duration::from_millis(300)).await;
1698                ok_response().await
1699            }
1700        }))
1701        .await;
1702        let addr = server.bound_tcp_address().await;
1703
1704        // Issue a request by hand rather than pulling in a client: all we need is for the handler to be running.
1705        let mut stream = TcpStream::connect(addr).await.expect("should connect");
1706        stream
1707            .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
1708            .await
1709            .expect("should write request");
1710
1711        while !handler_started.load(Ordering::SeqCst) {
1712            tokio::time::sleep(Duration::from_millis(5)).await;
1713        }
1714
1715        // Signal shutdown mid-request.
1716        server.signal_shutdown();
1717
1718        // The handler is still working, so the subtree must not be finished yet. This ordering is the actual assertion:
1719        // without the drain the subtree returns here and the response is abandoned.
1720        assert!(
1721            timeout(Duration::from_millis(50), &mut server.task).await.is_err(),
1722            "subtree should not finish while a request is still being served"
1723        );
1724
1725        let mut response = Vec::new();
1726        timeout(TEST_TIMEOUT, stream.read_to_end(&mut response))
1727            .await
1728            .expect("response should arrive")
1729            .expect("should read response");
1730        assert!(
1731            response.ends_with(b"ok"),
1732            "expected the in-flight response to complete, got {:?}",
1733            String::from_utf8_lossy(&response)
1734        );
1735
1736        let result = server.join().await;
1737        assert!(result.is_ok(), "subtree should stop cleanly after draining: {result:?}");
1738    }
1739
1740    #[tokio::test]
1741    async fn serves_http2_requests() {
1742        // The protocol is chosen from the bytes the client opens with rather than from ALPN, so a cleartext client that
1743        // knows to speak HTTP/2 gets HTTP/2. That is what lets one server carry both REST-ful routes and gRPC services,
1744        // since gRPC is HTTP/2 and nothing else.
1745        let harness = ServerTestHarness::start("http2-request", |supervisor, server_id| {
1746            let server = server_with(ListenAddress::tcp_loopback(0), ok_response).with_server_id(server_id);
1747            supervisor.add_worker(server.into_supervisor());
1748        })
1749        .await;
1750
1751        let address = harness.bound_tcp_address().await;
1752        let client = Client::builder(TokioExecutor::new())
1753            .http2_only(true)
1754            .build_http::<Empty<bytes::Bytes>>();
1755        let uri = format!("http://{address}/").parse().expect("should be a valid URI");
1756        let response = timeout(TEST_TIMEOUT, client.get(uri))
1757            .await
1758            .expect("server should answer an HTTP/2 request")
1759            .expect("request should succeed");
1760
1761        assert_eq!(response.version(), Version::HTTP_2);
1762        assert_eq!(response.status(), StatusCode::OK);
1763
1764        harness.shutdown().await;
1765    }
1766
1767    #[tokio::test]
1768    async fn an_http2_request_runs_as_a_supervised_child() {
1769        // `hyper` hands us one future per HTTP/2 stream -- so one per request -- and our executor turns each into a
1770        // dynamic child rather than a detached task. That is what puts per-request work in the process tree, and the
1771        // per-task poll metrics are where it becomes observable. The tag is the child's fully qualified process name,
1772        // scoped under the subtree's own supervisor ID.
1773        let recorder = TestRecorder::default();
1774        let _guard = metrics::set_default_local_recorder(&recorder);
1775
1776        // The recorder must be installed before anything is spawned: metric handles are resolved once, at spawn.
1777        let server = RunningServer::start(
1778            server_with(ListenAddress::tcp_loopback(0), ok_response).with_server_id("supervised-stream"),
1779        )
1780        .await;
1781        let addr = server.bound_tcp_address().await;
1782
1783        let client = Client::builder(TokioExecutor::new())
1784            .http2_only(true)
1785            .build_http::<Empty<bytes::Bytes>>();
1786        let uri = format!("http://{addr}/").parse().expect("should be a valid URI");
1787        let response = timeout(TEST_TIMEOUT, client.get(uri))
1788            .await
1789            .expect("server should answer an HTTP/2 request")
1790            .expect("request should succeed");
1791        assert_eq!(response.status(), StatusCode::OK);
1792
1793        server.shutdown().await;
1794
1795        let polls = recorder.counter((
1796            "runtime_task_poll_count",
1797            &[("task_name", "http_server_supervised_stream.http_conn_task")],
1798        ));
1799        assert!(
1800            polls.is_some_and(|polls| polls > 0),
1801            "the stream `hyper` executed should have run as a supervised child, got {polls:?}"
1802        );
1803    }
1804
1805    #[tokio::test]
1806    async fn http2_only_server_rejects_http1_requests() {
1807        // An endpoint that only serves gRPC has no use for HTTP/1.1, and rejecting it at the protocol level tells the
1808        // caller more than routing the request and answering with a 404 would.
1809        let server =
1810            RunningServer::start(server_with(ListenAddress::tcp_loopback(0), ok_response).with_http2_only()).await;
1811        let addr = server.bound_tcp_address().await;
1812
1813        let mut stream = TcpStream::connect(addr).await.expect("should connect");
1814        stream
1815            .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
1816            .await
1817            .expect("should write request");
1818
1819        // The connection is torn down rather than answered, which surfaces as either a clean EOF or a reset depending
1820        // on how far the peer got before the server gave up on it. Either is a rejection; what matters is that no
1821        // HTTP/1.1 response comes back.
1822        let mut response = Vec::new();
1823        let _ = timeout(TEST_TIMEOUT, stream.read_to_end(&mut response))
1824            .await
1825            .expect("server should close the connection rather than leaving the client hanging");
1826        assert!(
1827            !response.starts_with(b"HTTP/1.1"),
1828            "expected no HTTP/1.1 response, got {:?}",
1829            String::from_utf8_lossy(&response)
1830        );
1831
1832        server.shutdown().await;
1833    }
1834
1835    #[tokio::test]
1836    async fn an_idle_http2_peer_does_not_wedge_the_drain() {
1837        // The HTTP/2 counterpart of the half-sent request case: a peer that writes part of the connection preface and
1838        // then stalls never becomes idle, so `graceful_shutdown` alone will not close it.
1839        let server = RunningServer::start(
1840            server_with(ListenAddress::tcp_loopback(0), ok_response)
1841                .with_graceful_shutdown_timeout(Duration::from_secs(1)),
1842        )
1843        .await;
1844        let addr = server.bound_tcp_address().await;
1845
1846        let mut stream = TcpStream::connect(addr).await.expect("should connect");
1847        stream
1848            .write_all(b"PRI * HTTP/2.0\r\n")
1849            .await
1850            .expect("should write a partial HTTP/2 preface");
1851        stream.flush().await.expect("should flush");
1852        tokio::time::sleep(Duration::from_millis(100)).await;
1853
1854        server.shutdown().await;
1855    }
1856
1857    #[tokio::test]
1858    async fn retires_connections_that_reach_their_maximum_age() {
1859        // Nothing in the connection builder knows about connection age, so the server enforces the deadline itself.
1860        // Without it, a long-lived HTTP/2 client pins itself to whichever backend it first reached and stays there.
1861        let max_age = Duration::from_millis(300);
1862        let server = RunningServer::start(
1863            server_with(ListenAddress::tcp_loopback(0), ok_response)
1864                .with_http2_config(Http2Config::default().with_max_connection_age(max_age, None)),
1865        )
1866        .await;
1867        let addr = server.bound_tcp_address().await;
1868
1869        // Issue a request without asking for the connection to be closed, so what gets retired is a genuinely idle
1870        // keep-alive connection rather than one the client was finished with anyway.
1871        let started = Instant::now();
1872        let mut stream = TcpStream::connect(addr).await.expect("should connect");
1873        stream
1874            .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
1875            .await
1876            .expect("should write request");
1877
1878        let mut response = Vec::new();
1879        timeout(TEST_TIMEOUT, stream.read_to_end(&mut response))
1880            .await
1881            .expect("server should close the connection once it reaches its maximum age")
1882            .expect("should read the response");
1883
1884        assert!(
1885            response.ends_with(b"ok"),
1886            "expected the request to be served before the connection was retired, got {:?}",
1887            String::from_utf8_lossy(&response)
1888        );
1889        assert!(
1890            started.elapsed() >= max_age,
1891            "connection was closed after {:?}, before reaching its maximum age of {:?}",
1892            started.elapsed(),
1893            max_age
1894        );
1895
1896        server.shutdown().await;
1897    }
1898}