Skip to main content

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