1use 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
48const DEFAULT_GRPC_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(2 * 60 * 60);
50
51const DEFAULT_GRPC_KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(20);
53
54const DEFAULT_SERVER_NAME: &str = "http_server";
56
57const DEFAULT_DRAIN_DEADLINE: Duration = Duration::from_secs(30);
62
63const SHUTDOWN_TIMEOUT_SLACK: Duration = Duration::from_secs(1);
68
69const TLS_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
77
78const ACCEPTOR_TASK_NAME: &str = "acceptor";
80
81const TLS_HANDSHAKE_TASK_NAME: &str = "tls_handshake";
83
84const CONNECTION_TASK_NAME: &str = "http_conn";
86
87const CONNECTION_BG_TASK_NAME: &str = "http_conn_task";
89
90type ConnBuilder = Builder<SupervisedExecutor>;
92
93#[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 pub fn grpc_defaults() -> Self {
122 Self::default().with_keepalive(DEFAULT_GRPC_KEEPALIVE_INTERVAL, DEFAULT_GRPC_KEEPALIVE_TIMEOUT)
123 }
124
125 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 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 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
169pub 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 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 pub fn add_routes(mut self, routes: Router) -> Self {
261 self.http_routes = self.http_routes.merge(routes);
262 self
263 }
264
265 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 pub fn with_routes(mut self, routes: Router) -> Self {
285 self.router_override = Some(routes);
286 self
287 }
288
289 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 pub fn with_http2_config(mut self, config: Http2Config) -> Self {
307 self.http2_config = config;
308 self
309 }
310
311 pub fn with_http2_only(mut self) -> Self {
322 self.http2_only = true;
323 self
324 }
325
326 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 pub fn with_graceful_shutdown_timeout(mut self, timeout: Duration) -> Self {
357 self.graceful_shutdown_timeout = Some(timeout);
358 self
359 }
360
361 pub fn with_tls_config(mut self, config: ServerConfig) -> Self {
367 self.tls_config = Some(config);
368 self
369 }
370
371 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 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 supervisor.add_worker(place_on_pool(runtime::supervisable(acceptor), self.worker_pool.as_ref()).build());
418
419 supervisor
420 }
421}
422
423fn 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
433struct 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 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 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 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#[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 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 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
550async 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 Ok(stream) => {
571 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
594struct 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 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
644struct 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 ShutdownStrategy::Graceful(self.drain_deadline.saturating_add(SHUTDOWN_TIMEOUT_SLACK))
672 }
673
674 fn can_restart(&self) -> bool {
675 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
706fn 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 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#[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
808enum ConnectionAgeAction {
810 Retire,
812
813 Close,
815}
816
817enum ConnectionAgePhase {
819 Aging(Pin<Box<Sleep>>),
821
822 Grace(Pin<Box<Sleep>>),
824
825 Expired,
827}
828
829struct 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 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#[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 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
954fn 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 const TEST_TIMEOUT: Duration = Duration::from_secs(5);
1000
1001 struct RunningServer {
1003 supervisor: TestSupervisor,
1004 bound_address_id: Identifier,
1005 }
1006
1007 impl RunningServer {
1008 async fn start(server: HttpServer) -> Self {
1013 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 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 fn signal_shutdown(&mut self) {
1050 self.supervisor.signal_shutdown();
1051 }
1052
1053 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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 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 let stream = connect_unix(&socket_path).await;
1281 drop(stream);
1282
1283 harness.shutdown().await;
1284 }
1285
1286 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 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 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 #[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 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 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 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 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 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 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 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 harness.shutdown().await;
1548 }
1549
1550 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 server.signal_shutdown();
1854
1855 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 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 let recorder = TestRecorder::default();
1912 let _guard = metrics::set_default_local_recorder(&recorder);
1913
1914 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 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 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 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 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 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}