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::{response::IntoResponse, Router};
14use http::Request;
15use hyper::rt::{Read, Write};
16use hyper_util::{
17 rt::{TokioIo, TokioTimer},
18 server::conn::auto::Builder,
19};
20use pin_project_lite::pin_project;
21use rustls::ServerConfig;
22use saluki_common::sync::shutdown::ShutdownHandle;
23use saluki_core::runtime::{
24 self,
25 state::{DataspaceRegistry, Identifier},
26 BuilderState, ChildBuilder, InitializationError, ShutdownStrategy, Supervisable, Supervisor, SupervisorFuture,
27 SupervisorHandle,
28};
29use saluki_error::{ErrorContext as _, GenericError};
30use saluki_tls::ensure_server_config_fips_compliant;
31use stringtheory::MetaString;
32use tokio::{
33 pin,
34 runtime::Handle,
35 select,
36 time::{sleep, timeout, Sleep},
37};
38use tokio_rustls::TlsAcceptor;
39use tonic::{body::Body as GrpcBody, server::NamedService, service::Routes};
40use tower::{util::Oneshot, Service, ServiceExt as _};
41use tracing::{debug, error, info, warn};
42
43use crate::net::{
44 listener::ConnectionOrientedListener, server::grpc::merge_grpc_routes, stream::Connection, ListenAddress,
45};
46
47const DEFAULT_GRPC_KEEPALIVE_INTERVAL: Duration = Duration::from_secs(2 * 60 * 60);
49
50const DEFAULT_GRPC_KEEPALIVE_TIMEOUT: Duration = Duration::from_secs(20);
52
53const DEFAULT_SERVER_NAME: &str = "http_server";
55
56const DEFAULT_DRAIN_DEADLINE: Duration = Duration::from_secs(30);
61
62const SHUTDOWN_BUDGET_SLACK: Duration = Duration::from_secs(1);
69
70const TLS_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
78
79const ACCEPTOR_TASK_NAME: &str = "acceptor";
81
82const TLS_HANDSHAKE_TASK_NAME: &str = "tls_handshake";
84
85const CONNECTION_TASK_NAME: &str = "http_conn";
87
88const CONNECTION_BG_TASK_NAME: &str = "http_conn_task";
90
91type ConnBuilder = Builder<SupervisedExecutor>;
93
94#[derive(Clone, Copy, Debug, Default)]
105pub struct Http2Config {
106 keepalive_interval: Option<Duration>,
107 keepalive_timeout: Option<Duration>,
108 max_connection_age: Option<Duration>,
109 max_connection_age_grace: Option<Duration>,
110 max_concurrent_streams: Option<u32>,
111}
112
113impl Http2Config {
114 pub fn grpc_defaults() -> Self {
123 Self::default().with_keepalive(DEFAULT_GRPC_KEEPALIVE_INTERVAL, DEFAULT_GRPC_KEEPALIVE_TIMEOUT)
124 }
125
126 pub fn with_keepalive(mut self, interval: Duration, timeout: Duration) -> Self {
135 self.keepalive_interval = Some(interval);
136 self.keepalive_timeout = Some(timeout);
137 self
138 }
139
140 pub fn with_max_connection_age(mut self, max_age: Duration, grace: Option<Duration>) -> Self {
150 self.max_connection_age = Some(max_age);
151 self.max_connection_age_grace = grace;
152 self
153 }
154
155 pub fn with_max_concurrent_streams(mut self, max_concurrent_streams: u32) -> Self {
165 self.max_concurrent_streams = Some(max_concurrent_streams);
166 self
167 }
168}
169
170pub struct HttpServer {
213 listen_address: ListenAddress,
214 tls_config: Option<ServerConfig>,
215 http2_config: Http2Config,
216 http2_only: bool,
217 graceful_shutdown_timeout: Option<Duration>,
218 server_id: Option<MetaString>,
219 name: MetaString,
220 http_routes: Router,
221 grpc_routes: Option<Routes>,
222 router_override: Option<Router>,
223 worker_pool: Option<Handle>,
224}
225
226impl HttpServer {
227 pub fn from_listen_address(listen_address: ListenAddress) -> Self {
229 Self {
230 listen_address,
231 tls_config: None,
232 http2_config: Http2Config::default(),
233 http2_only: false,
234 graceful_shutdown_timeout: None,
235 server_id: None,
236 name: MetaString::from_static(DEFAULT_SERVER_NAME),
237 http_routes: Router::new(),
238 grpc_routes: None,
239 router_override: None,
240 worker_pool: None,
241 }
242 }
243
244 pub fn add_routes(mut self, routes: Router) -> Self {
252 self.http_routes = self.http_routes.merge(routes);
253 self
254 }
255
256 pub fn add_grpc_service<S>(mut self, service: S) -> Self
263 where
264 S: Service<Request<GrpcBody>, Error = Infallible> + NamedService + Clone + Send + Sync + 'static,
265 S::Response: IntoResponse,
266 S::Future: Send + 'static,
267 {
268 self.grpc_routes = Some(self.grpc_routes.take().unwrap_or_default().add_service(service));
269 self
270 }
271
272 pub fn with_routes(mut self, routes: Router) -> Self {
276 self.router_override = Some(routes);
277 self
278 }
279
280 fn build_router(&self) -> Router {
282 if let Some(router) = &self.router_override {
283 return router.clone();
284 }
285
286 let mut router = self.http_routes.clone();
287 if let Some(grpc_routes) = self.grpc_routes.clone() {
288 router = merge_grpc_routes(router, grpc_routes);
289 }
290
291 router
292 }
293
294 pub fn with_http2_config(mut self, config: Http2Config) -> Self {
298 self.http2_config = config;
299 self
300 }
301
302 pub fn with_http2_only(mut self) -> Self {
313 self.http2_only = true;
314 self
315 }
316
317 pub fn with_server_id(mut self, id: impl Into<MetaString>) -> Self {
327 let id = id.into();
328
329 self.name = MetaString::from(format!("{}_{}", DEFAULT_SERVER_NAME, id));
330 self.server_id = Some(id);
331 self
332 }
333
334 pub fn with_graceful_shutdown_timeout(mut self, timeout: Duration) -> Self {
348 self.graceful_shutdown_timeout = Some(timeout);
349 self
350 }
351
352 pub fn with_tls_config(mut self, config: ServerConfig) -> Self {
358 self.tls_config = Some(config);
359 self
360 }
361
362 pub fn with_worker_pool(mut self, handle: Handle) -> Self {
374 self.worker_pool = Some(handle);
375 self
376 }
377
378 fn get_server_id(&self) -> Option<Identifier> {
379 self.server_id.clone().map(|sid| get_bound_address_id(&sid))
380 }
381
382 pub fn into_supervisor(self) -> Supervisor {
387 let service = self.build_router();
388 let bound_address_id = self.get_server_id();
389
390 let drain_deadline = self.graceful_shutdown_timeout.unwrap_or(DEFAULT_DRAIN_DEADLINE);
391 let mut supervisor = Supervisor::new(&*self.name)
392 .expect("server name is derived from a non-empty constant")
393 .with_shutdown_budget(drain_deadline.saturating_add(SHUTDOWN_BUDGET_SLACK));
394
395 let acceptor = Acceptor {
396 listen_address: self.listen_address,
397 bound_address_id,
398 tls_config: self.tls_config,
399 http2_config: self.http2_config,
400 http2_only: self.http2_only,
401 drain_deadline,
402 service,
403 supervisor: supervisor.handle(),
404 worker_pool: self.worker_pool.clone(),
405 };
406
407 supervisor.add_worker(place_on_pool(runtime::supervisable(acceptor), self.worker_pool.as_ref()).build());
411
412 supervisor
413 }
414}
415
416fn place_on_pool<'a, S: BuilderState>(
418 builder: ChildBuilder<'a, S>, worker_pool: Option<&Handle>,
419) -> ChildBuilder<'a, S> {
420 match worker_pool {
421 Some(pool) => builder.on_runtime(pool.clone()),
422 None => builder,
423 }
424}
425
426struct Acceptor {
428 listen_address: ListenAddress,
429 bound_address_id: Option<Identifier>,
430 tls_config: Option<ServerConfig>,
431 http2_config: Http2Config,
432 http2_only: bool,
433 drain_deadline: Duration,
434 service: Router,
435 supervisor: SupervisorHandle,
436 worker_pool: Option<Handle>,
437}
438
439#[async_trait]
440impl Supervisable for Acceptor {
441 fn name(&self) -> &str {
442 ACCEPTOR_TASK_NAME
443 }
444
445 async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
446 let listener = ConnectionOrientedListener::from_listen_address(self.listen_address.clone())
448 .await
449 .with_error_context(|| format!("Failed to bind listener for HTTP server ({}).", self.listen_address))?;
450
451 if let Some(bound_address_id) = self.bound_address_id.clone() {
453 let dataspace = DataspaceRegistry::try_current()
454 .ok_or_else(|| saluki_error::generic_error!("Dataspace not available for HTTP server."))?;
455
456 dataspace.assert(listener.bound_listen_address(), bound_address_id);
457 }
458
459 let conn_builder = build_conn_builder(
460 SupervisedExecutor::new(self.supervisor.clone(), self.worker_pool.clone()),
461 self.http2_config,
462 self.http2_only,
463 );
464
465 let maybe_tls_acceptor = match self.tls_config.clone() {
469 Some(mut config) => {
470 config.alpn_protocols = alpn_protocols(&conn_builder);
471 ensure_server_config_fips_compliant(&mut config)?;
472
473 Some(TlsAcceptor::from(Arc::new(config)))
474 }
475 None => None,
476 };
477
478 let context = ConnectionContext {
479 conn_builder,
480 service: self.service.clone(),
481 listen_addr: listener.listen_address().clone(),
482 drain_deadline: self.drain_deadline,
483 http2_config: self.http2_config,
484 supervisor: self.supervisor.clone(),
485 worker_pool: self.worker_pool.clone(),
486 };
487
488 Ok(Box::pin(run_accept_loop(
489 listener,
490 context,
491 maybe_tls_acceptor,
492 process_shutdown,
493 )))
494 }
495}
496
497#[derive(Clone)]
499struct ConnectionContext {
500 conn_builder: ConnBuilder,
501 service: Router,
502 listen_addr: ListenAddress,
503 drain_deadline: Duration,
504 http2_config: Http2Config,
505 supervisor: SupervisorHandle,
506 worker_pool: Option<Handle>,
507}
508
509impl ConnectionContext {
510 fn spawn_connection<I>(&self, io: I)
512 where
513 I: Read + Write + Unpin + Send + 'static,
514 {
515 let connection = HttpConnection {
516 conn_builder: self.conn_builder.clone(),
517 io: Mutex::new(Some(io)),
518 service: self.service.clone(),
519 listen_addr: self.listen_addr.clone(),
520 drain_deadline: self.drain_deadline,
521 http2_config: self.http2_config,
522 };
523
524 let builder = self.supervisor.supervisable(connection).temporary();
525 place_on_pool(builder, self.worker_pool.as_ref())
526 .with_budget_bounded_shutdown()
527 .spawn();
528 }
529
530 fn spawn_handshake(&self, acceptor: TlsAcceptor, stream: Connection) {
532 let handshake = TlsHandshake {
533 acceptor,
534 stream: Mutex::new(Some(stream)),
535 context: self.clone(),
536 };
537
538 let builder = self.supervisor.supervisable(handshake).temporary();
539 place_on_pool(builder, self.worker_pool.as_ref())
540 .with_budget_bounded_shutdown()
541 .spawn();
542 }
543}
544
545async fn run_accept_loop(
550 mut listener: ConnectionOrientedListener, context: ConnectionContext, maybe_tls_acceptor: Option<TlsAcceptor>,
551 shutdown: ShutdownHandle,
552) -> Result<(), GenericError> {
553 let tls_enabled = maybe_tls_acceptor.is_some();
554 let listen_addr = context.listen_addr.clone();
555
556 info!(%listen_addr, tls_enabled, "HTTP server started.");
557
558 pin!(shutdown);
559
560 let result = loop {
561 select! {
562 result = listener.accept() => match result {
563 Ok(stream) => match &maybe_tls_acceptor {
566 Some(acceptor) => context.spawn_handshake(acceptor.clone(), stream),
567 None => context.spawn_connection(TokioIo::new(stream)),
568 },
569 Err(e) => break Err(GenericError::from(e)),
570 },
571
572 _ = &mut shutdown => {
573 debug!(%listen_addr, "Received shutdown signal.");
574 break Ok(());
575 }
576 }
577 };
578
579 info!(%listen_addr, "HTTP server stopped accepting connections.");
580
581 result
582}
583
584struct TlsHandshake {
590 acceptor: TlsAcceptor,
591 stream: Mutex<Option<Connection>>,
592 context: ConnectionContext,
593}
594
595#[async_trait]
596impl Supervisable for TlsHandshake {
597 fn name(&self) -> &str {
598 TLS_HANDSHAKE_TASK_NAME
599 }
600
601 async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
602 let stream = take_once(&self.stream, "TLS handshake")?;
603 let acceptor = self.acceptor.clone();
604 let context = self.context.clone();
605
606 Ok(Box::pin(async move {
607 let listen_addr = context.listen_addr.clone();
608
609 select! {
610 result = timeout(TLS_HANDSHAKE_TIMEOUT, acceptor.accept(stream)) => match result {
611 Ok(Ok(stream)) => context.spawn_connection(TokioIo::new(stream)),
612 Ok(Err(e)) => error!(%listen_addr, error = %e, "Failed to complete TLS handshake."),
613 Err(_) => warn!(
614 %listen_addr,
615 "Abandoning TLS handshake that did not complete within {:?}.", TLS_HANDSHAKE_TIMEOUT
616 ),
617 },
618
619 _ = process_shutdown => debug!(%listen_addr, "Abandoning in-flight TLS handshake at shutdown."),
620 }
621
622 Ok(())
623 }))
624 }
625}
626
627struct HttpConnection<I> {
634 conn_builder: ConnBuilder,
635 io: Mutex<Option<I>>,
636 service: Router,
637 listen_addr: ListenAddress,
638 drain_deadline: Duration,
639 http2_config: Http2Config,
640}
641
642#[async_trait]
643impl<I> Supervisable for HttpConnection<I>
644where
645 I: Read + Write + Unpin + Send + 'static,
646{
647 fn name(&self) -> &str {
648 CONNECTION_TASK_NAME
649 }
650
651 fn shutdown_strategy(&self) -> ShutdownStrategy {
652 ShutdownStrategy::Graceful(Duration::MAX)
655 }
656
657 async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
658 let io = take_once(&self.io, "HTTP connection")?;
659 let conn_builder = self.conn_builder.clone();
660 let service = self.service.clone();
661 let listen_addr = self.listen_addr.clone();
662 let drain_deadline = self.drain_deadline;
663 let http2_config = self.http2_config;
664
665 Ok(Box::pin(async move {
666 drive_connection(
667 conn_builder,
668 io,
669 service,
670 listen_addr,
671 process_shutdown,
672 drain_deadline,
673 http2_config,
674 )
675 .await;
676
677 Ok(())
678 }))
679 }
680}
681
682fn take_once<T>(slot: &Mutex<Option<T>>, what: &str) -> Result<T, InitializationError> {
687 slot.lock()
688 .expect("single-use worker mutex poisoned")
689 .take()
690 .ok_or_else(|| {
691 InitializationError::from(saluki_error::generic_error!("{} can only be initialized once.", what))
692 })
693}
694
695async fn drive_connection<I>(
696 conn_builder: ConnBuilder, io: I, service: Router, listen_addr: ListenAddress, shutdown: ShutdownHandle,
697 drain_deadline: Duration, http2_config: Http2Config,
698) where
699 I: Read + Write + Unpin + Send + 'static,
700{
701 let service = TowerToHyperService::new(service);
702 let conn = conn_builder.serve_connection(io, service);
703 let mut connection_age = ConnectionAge::new(&http2_config);
704 pin!(conn, shutdown);
705
706 loop {
707 select! {
708 result = conn.as_mut() => {
709 if let Err(e) = result {
710 error!(%listen_addr, error = %e, "Failed to serve HTTP connection.");
711 }
712
713 return;
714 },
715
716 action = connection_age.next_action() => match action {
717 ConnectionAgeAction::Retire => {
718 debug!(%listen_addr, "Retiring HTTP connection that reached its maximum age.");
719
720 conn.as_mut().graceful_shutdown();
721 },
722 ConnectionAgeAction::Close => {
723 warn!(%listen_addr, "Forcibly closing HTTP connection that did not retire within its grace period.");
724
725 return;
726 },
727 },
728
729 _ = &mut shutdown => {
730 debug!(%listen_addr, "Draining HTTP connection.");
731
732 conn.as_mut().graceful_shutdown();
733
734 match timeout(drain_deadline, conn.as_mut()).await {
735 Ok(Ok(())) => {},
736 Ok(Err(e)) => warn!(%listen_addr, error = %e, "Failed to drain HTTP connection."),
737 Err(_) => warn!(%listen_addr, "Failed to gracefully drain HTTP connection after {:?}.", drain_deadline)
738 }
739
740 return;
741 },
742 }
743 }
744}
745
746#[derive(Clone)]
755struct SupervisedExecutor {
756 supervisor: SupervisorHandle,
757 worker_pool: Option<Handle>,
758}
759
760impl SupervisedExecutor {
761 fn new(supervisor: SupervisorHandle, worker_pool: Option<Handle>) -> Self {
762 Self {
763 supervisor,
764 worker_pool,
765 }
766 }
767}
768
769impl<F> hyper::rt::Executor<F> for SupervisedExecutor
770where
771 F: Future<Output = ()> + Send + 'static,
772{
773 fn execute(&self, fut: F) {
774 let builder = self.supervisor.worker(CONNECTION_BG_TASK_NAME, fut);
775 place_on_pool(builder, self.worker_pool.as_ref()).spawn();
776 }
777}
778
779enum ConnectionAgeAction {
781 Retire,
783
784 Close,
786}
787
788enum ConnectionAgePhase {
790 Aging(Pin<Box<Sleep>>),
792
793 Grace(Pin<Box<Sleep>>),
795
796 Expired,
798}
799
800struct ConnectionAge {
806 phase: ConnectionAgePhase,
807 grace: Option<Duration>,
808}
809
810impl ConnectionAge {
811 fn new(http2_config: &Http2Config) -> Self {
812 Self {
813 phase: match http2_config.max_connection_age {
814 Some(max_age) => ConnectionAgePhase::Aging(Box::pin(sleep(max_age))),
815 None => ConnectionAgePhase::Expired,
816 },
817 grace: http2_config.max_connection_age_grace,
818 }
819 }
820
821 async fn next_action(&mut self) -> ConnectionAgeAction {
822 match &mut self.phase {
823 ConnectionAgePhase::Aging(deadline) => {
824 deadline.as_mut().await;
825
826 self.phase = match self.grace {
829 Some(grace) => ConnectionAgePhase::Grace(Box::pin(sleep(grace))),
830 None => ConnectionAgePhase::Expired,
831 };
832
833 ConnectionAgeAction::Retire
834 }
835
836 ConnectionAgePhase::Grace(deadline) => {
837 deadline.as_mut().await;
838 self.phase = ConnectionAgePhase::Expired;
839
840 ConnectionAgeAction::Close
841 }
842
843 ConnectionAgePhase::Expired => std::future::pending().await,
844 }
845 }
846}
847
848#[derive(Debug, Copy, Clone)]
850struct TowerToHyperService<S> {
851 service: S,
852}
853
854impl<S> TowerToHyperService<S> {
855 fn new(tower_service: S) -> Self {
856 Self { service: tower_service }
857 }
858}
859
860impl<S, R> hyper::service::Service<R> for TowerToHyperService<S>
861where
862 S: tower::Service<R> + Clone,
863{
864 type Response = S::Response;
865 type Error = S::Error;
866 type Future = TowerToHyperServiceFuture<S, R>;
867
868 fn call(&self, req: R) -> Self::Future {
869 TowerToHyperServiceFuture {
870 future: self.service.clone().oneshot(req),
871 }
872 }
873}
874
875pin_project! {
876 struct TowerToHyperServiceFuture<S, R>
878 where
879 S: tower::Service<R>,
880 {
881 #[pin]
882 future: Oneshot<S, R>,
883 }
884}
885
886impl<S, R> Future for TowerToHyperServiceFuture<S, R>
887where
888 S: tower::Service<R>,
889{
890 type Output = Result<S::Response, S::Error>;
891
892 #[inline]
893 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
894 self.project().future.poll(cx)
895 }
896}
897
898fn build_conn_builder(executor: SupervisedExecutor, http2_config: Http2Config, http2_only: bool) -> ConnBuilder {
899 let mut builder = Builder::new(executor);
900 builder
901 .http1()
902 .timer(TokioTimer::new())
903 .header_read_timeout(Duration::from_secs(10));
904
905 builder
906 .http2()
907 .timer(TokioTimer::new())
908 .keep_alive_interval(http2_config.keepalive_interval);
909
910 if let Some(keepalive_timeout) = http2_config.keepalive_timeout {
911 builder.http2().keep_alive_timeout(keepalive_timeout);
912 }
913
914 if let Some(max_concurrent_streams) = http2_config.max_concurrent_streams {
915 builder.http2().max_concurrent_streams(max_concurrent_streams);
916 }
917
918 if http2_only {
919 builder = builder.http2_only();
920 }
921
922 builder
923}
924
925fn alpn_protocols(conn_builder: &ConnBuilder) -> Vec<Vec<u8>> {
927 let mut protocols = vec![];
928
929 if conn_builder.is_http2_available() {
930 protocols.push(b"h2".to_vec());
931 }
932
933 if conn_builder.is_http1_available() {
934 protocols.push(b"http/1.1".to_vec());
935 }
936
937 protocols
938}
939
940fn get_bound_address_id(server_id: &str) -> Identifier {
941 Identifier::from(format!("http-server-{}", server_id))
942}
943
944#[cfg(test)]
945mod tests {
946 use std::net::{SocketAddr, TcpListener as StdTcpListener};
947 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
948
949 use http::{Response, StatusCode, Version};
950 use http_body_util::{Empty, Full};
951 use hyper_util::client::legacy::Client;
952 use hyper_util::rt::TokioExecutor;
953 use saluki_core::runtime::state::{DataspaceUpdate, IdentifierFilter};
954 use saluki_core::runtime::SupervisorError;
955 use saluki_metrics::test::TestRecorder;
956 use saluki_tls::test_util::SelfSignedCert;
957 use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
958 use tokio::net::TcpStream;
959 use tokio::sync::oneshot;
960 use tokio::task::JoinHandle;
961 use tokio::time::{timeout, Instant};
962 use tower::util::service_fn;
963
964 use super::*;
965 use crate::net::addr::BoundListenAddress;
966 #[cfg(unix)]
967 use crate::net::server::test_util::connect_unix;
968 use crate::net::server::test_util::{connect_tcp, DataspaceCapture, ServerTestHarness};
969
970 const TEST_TIMEOUT: Duration = Duration::from_secs(5);
972
973 struct RunningServer {
975 dataspace_rx: Mutex<Option<oneshot::Receiver<DataspaceRegistry>>>,
979 bound_address_id: Identifier,
980 shutdown_tx: Option<oneshot::Sender<()>>,
981 task: JoinHandle<Result<(), SupervisorError>>,
982 }
983
984 impl RunningServer {
985 async fn start(server: HttpServer) -> Self {
990 static NEXT_ID: AtomicU64 = AtomicU64::new(0);
993 let server_id = server.server_id.clone().unwrap_or_else(|| {
994 MetaString::from(format!("running-server-{}", NEXT_ID.fetch_add(1, Ordering::Relaxed)))
995 });
996 let server = server.with_server_id(server_id.clone());
997
998 let (capture, dataspace_rx) = DataspaceCapture::new();
999 let mut supervisor = server.into_supervisor();
1000 supervisor.add_worker(capture);
1001
1002 let (shutdown_tx, shutdown_rx) = oneshot::channel();
1003 let task = tokio::spawn(async move { supervisor.run_with_shutdown(shutdown_rx).await });
1004
1005 Self {
1006 dataspace_rx: Mutex::new(Some(dataspace_rx)),
1007 bound_address_id: get_bound_address_id(&server_id),
1008 shutdown_tx: Some(shutdown_tx),
1009 task,
1010 }
1011 }
1012
1013 async fn bound_tcp_address(&self) -> SocketAddr {
1015 let dataspace_rx = self
1016 .dataspace_rx
1017 .lock()
1018 .expect("dataspace receiver lock should not be poisoned")
1019 .take()
1020 .expect("the bound address should only be looked up once");
1021 let dataspace = timeout(TEST_TIMEOUT, dataspace_rx)
1022 .await
1023 .expect("should capture the supervisor dataspace")
1024 .expect("dataspace capture worker should send the registry");
1025
1026 let mut subscription =
1027 dataspace.subscribe::<BoundListenAddress>(IdentifierFilter::exact(self.bound_address_id.clone()));
1028
1029 match timeout(TEST_TIMEOUT, subscription.recv()).await {
1030 Ok(Some(DataspaceUpdate::Asserted(_, BoundListenAddress::Tcp(addr)))) => addr,
1031 update => panic!(
1032 "expected a bound TCP address assertion for '{:?}', got {update:?}",
1033 self.bound_address_id
1034 ),
1035 }
1036 }
1037
1038 fn signal_shutdown(&mut self) {
1040 let _ = self.shutdown_tx.take().expect("should only shut down once").send(());
1041 }
1042
1043 async fn join(self) -> Result<(), SupervisorError> {
1045 timeout(TEST_TIMEOUT, self.task)
1046 .await
1047 .expect("server should stop before timeout")
1048 .expect("server task should not panic")
1049 }
1050
1051 async fn shutdown(mut self) {
1053 self.signal_shutdown();
1054 let result = self.join().await;
1055 assert!(result.is_ok(), "server should stop cleanly: {result:?}");
1056 }
1057 }
1058
1059 fn test_conn_builder(http2_only: bool) -> ConnBuilder {
1061 let supervisor = Supervisor::new("alpn-test").expect("test supervisor name should be valid");
1062
1063 build_conn_builder(
1064 SupervisedExecutor::new(supervisor.handle(), None),
1065 Http2Config::default(),
1066 http2_only,
1067 )
1068 }
1069
1070 fn free_local_addr() -> SocketAddr {
1075 let listener = StdTcpListener::bind("127.0.0.1:0").expect("should bind an ephemeral port");
1076 let addr = listener.local_addr().expect("should have a local address");
1077 drop(listener);
1078 addr
1079 }
1080
1081 fn server_with<F, Fut>(listen_address: ListenAddress, f: F) -> HttpServer
1083 where
1084 F: Fn() -> Fut + Send + Sync + Clone + 'static,
1085 Fut: Future<Output = Result<Response<Full<bytes::Bytes>>, Infallible>> + Send + 'static,
1086 {
1087 HttpServer::from_listen_address(listen_address).add_routes(routes_answering_with(f))
1088 }
1089
1090 fn routes_answering_with<F, Fut>(f: F) -> Router
1092 where
1093 F: Fn() -> Fut + Send + Sync + Clone + 'static,
1094 Fut: Future<Output = Result<Response<Full<bytes::Bytes>>, Infallible>> + Send + 'static,
1095 {
1096 Router::new().fallback_service(service_fn(move |_req: axum::extract::Request| f()))
1097 }
1098
1099 async fn ok_response() -> Result<Response<Full<bytes::Bytes>>, Infallible> {
1101 Ok(Response::new(Full::new(bytes::Bytes::from_static(b"ok"))))
1102 }
1103
1104 async fn route_request(router: Router, uri: &str, content_type: Option<&str>) -> Response<axum::body::Body> {
1106 let mut builder = Request::builder().uri(uri);
1107 if let Some(content_type) = content_type {
1108 builder = builder.header(http::header::CONTENT_TYPE, content_type);
1109 }
1110
1111 let request = builder
1112 .body(axum::body::Body::empty())
1113 .expect("should build the request");
1114
1115 router.oneshot(request).await.expect("router should answer")
1116 }
1117
1118 #[derive(Clone)]
1120 struct EmptyGrpcService;
1121
1122 impl NamedService for EmptyGrpcService {
1123 const NAME: &'static str = "test.EmptyService";
1124 }
1125
1126 impl Service<Request<GrpcBody>> for EmptyGrpcService {
1127 type Response = Response<axum::body::Body>;
1128 type Error = Infallible;
1129 type Future = std::future::Ready<Result<Self::Response, Infallible>>;
1130
1131 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1132 Poll::Ready(Ok(()))
1133 }
1134
1135 fn call(&mut self, _req: Request<GrpcBody>) -> Self::Future {
1136 std::future::ready(Ok(Response::new(axum::body::Body::empty())))
1137 }
1138 }
1139
1140 #[tokio::test]
1141 async fn accumulated_http_and_grpc_routes_are_served_together() {
1142 let router = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1143 .add_routes(Router::new().route("/first", axum::routing::get(|| async { "first" })))
1144 .add_routes(Router::new().route("/second", axum::routing::get(|| async { "second" })))
1145 .add_grpc_service(EmptyGrpcService)
1146 .build_router();
1147
1148 for path in ["/first", "/second"] {
1150 let response = route_request(router.clone(), path, None).await;
1151 assert_eq!(response.status(), StatusCode::OK, "{path} should be served");
1152 }
1153
1154 let grpc_response = route_request(router.clone(), "/test.EmptyService/Method", Some("application/grpc")).await;
1155 assert_eq!(grpc_response.status(), StatusCode::OK);
1156
1157 let unmatched_grpc = route_request(router.clone(), "/test.Missing/Method", Some("application/grpc")).await;
1160 assert_eq!(
1161 unmatched_grpc
1162 .headers()
1163 .get("grpc-status")
1164 .and_then(|v| v.to_str().ok()),
1165 Some("12")
1166 );
1167
1168 let unmatched_http = route_request(router, "/nowhere", None).await;
1169 assert_eq!(unmatched_http.status(), StatusCode::NOT_FOUND);
1170 }
1171
1172 #[test]
1173 fn the_subtree_name_reflects_the_server_id() {
1174 let unnamed = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0)).into_supervisor();
1179 assert_eq!(unnamed.id(), DEFAULT_SERVER_NAME);
1180
1181 let grpc = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1182 .with_server_id("otlp-grpc")
1183 .into_supervisor();
1184 let http = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1185 .with_server_id("otlp-http")
1186 .into_supervisor();
1187 assert_eq!(grpc.id(), "http_server_otlp-grpc");
1188 assert_eq!(http.id(), "http_server_otlp-http");
1189 }
1190
1191 #[tokio::test]
1192 async fn an_http_only_server_keeps_its_own_fallback() {
1193 let router = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1196 .add_routes(Router::new().fallback(|| async { StatusCode::IM_A_TEAPOT }))
1197 .build_router();
1198
1199 let response = route_request(router, "/anything", None).await;
1200 assert_eq!(response.status(), StatusCode::IM_A_TEAPOT);
1201 }
1202
1203 #[tokio::test]
1204 async fn overriding_the_router_discards_accumulated_routes() {
1205 let router = HttpServer::from_listen_address(ListenAddress::tcp_loopback(0))
1206 .add_routes(Router::new().route("/added", axum::routing::get(|| async { "added" })))
1207 .add_grpc_service(EmptyGrpcService)
1208 .with_routes(Router::new().route("/override", axum::routing::get(|| async { "override" })))
1209 .build_router();
1210
1211 let overridden = route_request(router.clone(), "/override", None).await;
1212 assert_eq!(overridden.status(), StatusCode::OK);
1213
1214 let added = route_request(router.clone(), "/added", None).await;
1216 assert_eq!(added.status(), StatusCode::NOT_FOUND);
1217
1218 let grpc = route_request(router, "/test.EmptyService/Method", Some("application/grpc")).await;
1219 assert_eq!(grpc.status(), StatusCode::NOT_FOUND);
1220 }
1221
1222 #[tokio::test]
1223 async fn publishes_bound_tcp_address() {
1224 let harness = ServerTestHarness::start("http-tcp-bound-address", |supervisor, server_id| {
1225 let server = server_with(ListenAddress::tcp_loopback(0), ok_response).with_server_id(server_id);
1226 supervisor.add_worker(server.into_supervisor());
1227 })
1228 .await;
1229
1230 let local_tcp_address = match harness.bound_address().await {
1231 BoundListenAddress::Tcp(addr) => addr,
1232 other_addr => panic!("expected TCP address, got {:?}", other_addr),
1233 };
1234
1235 assert_ne!(local_tcp_address.port(), 0);
1236
1237 let stream = connect_tcp(local_tcp_address).await;
1242 drop(stream);
1243
1244 harness.shutdown().await;
1245 }
1246
1247 #[cfg(unix)]
1248 #[tokio::test]
1249 async fn publishes_bound_unix_address() {
1250 let tempdir = tempfile::tempdir().expect("should create temp dir");
1251 let socket_path = tempdir.path().join("http.sock");
1252 let listen_address = ListenAddress::Unix(socket_path.clone());
1253
1254 let harness = ServerTestHarness::start("http-unix-bound-address", move |supervisor, server_id| {
1255 let server = server_with(listen_address, ok_response).with_server_id(server_id);
1256 supervisor.add_worker(server.into_supervisor());
1257 })
1258 .await;
1259
1260 let local_unix_address = match harness.bound_address().await {
1261 BoundListenAddress::Unix(addr) => addr,
1262 other_addr => panic!("expected UDS address, got {:?}", other_addr),
1263 };
1264
1265 assert_eq!(socket_path, local_unix_address);
1266
1267 let stream = connect_unix(&socket_path).await;
1272 drop(stream);
1273
1274 harness.shutdown().await;
1275 }
1276
1277 fn server_tls_config(cert: &SelfSignedCert) -> ServerConfig {
1279 ServerConfig::builder()
1280 .with_no_client_auth()
1281 .with_single_cert(cert.cert_chain(), cert.private_key())
1282 .expect("should build TLS config")
1283 }
1284
1285 async fn negotiate_alpn(
1287 address: SocketAddr, cert_chain: Vec<rustls::pki_types::CertificateDer<'static>>, client_alpn: &[&[u8]],
1288 ) -> std::io::Result<Option<Vec<u8>>> {
1289 let mut roots = rustls::RootCertStore::empty();
1290 for cert in cert_chain {
1291 roots.add(cert).expect("should trust the self-signed cert");
1292 }
1293
1294 let mut client_config = rustls::ClientConfig::builder()
1295 .with_root_certificates(roots)
1296 .with_no_client_auth();
1297 client_config.alpn_protocols = client_alpn.iter().map(|protocol| protocol.to_vec()).collect();
1298
1299 let stream = TcpStream::connect(address).await.expect("should connect");
1300 let server_name = rustls::pki_types::ServerName::try_from("localhost").expect("should be a valid server name");
1301 let tls_stream = tokio_rustls::TlsConnector::from(Arc::new(client_config))
1302 .connect(server_name, stream)
1303 .await?;
1304
1305 Ok(tls_stream.get_ref().1.alpn_protocol().map(ToOwned::to_owned))
1306 }
1307
1308 async fn start_tls_server(
1310 harness_id: &str, http2_only: bool,
1311 ) -> (
1312 ServerTestHarness,
1313 SocketAddr,
1314 Vec<rustls::pki_types::CertificateDer<'static>>,
1315 ) {
1316 let _ = saluki_tls::initialize_default_crypto_provider();
1317 let cert = SelfSignedCert::localhost();
1318 let cert_chain = cert.cert_chain();
1319 let tls_config = server_tls_config(&cert);
1320
1321 let harness = ServerTestHarness::start(harness_id, move |supervisor, server_id| {
1322 let mut server = server_with(ListenAddress::tcp_loopback(0), ok_response)
1323 .with_tls_config(tls_config)
1324 .with_server_id(server_id);
1325 if http2_only {
1326 server = server.with_http2_only();
1327 }
1328 supervisor.add_worker(server.into_supervisor());
1329 })
1330 .await;
1331
1332 let address = match harness.bound_address().await {
1333 BoundListenAddress::Tcp(addr) => addr,
1334 other_addr => panic!("expected TCP address, got {:?}", other_addr),
1335 };
1336
1337 (harness, address, cert_chain)
1338 }
1339
1340 #[test]
1341 fn advertised_alpn_protocols_track_the_protocol_restriction() {
1342 let conn_builder_both = test_conn_builder(false);
1343 assert_eq!(
1344 alpn_protocols(&conn_builder_both),
1345 vec![b"h2".to_vec(), b"http/1.1".to_vec()]
1346 );
1347
1348 let conn_builder_http2_only = test_conn_builder(true);
1349 assert_eq!(alpn_protocols(&conn_builder_http2_only), vec![b"h2".to_vec()]);
1350 }
1351
1352 #[tokio::test]
1353 async fn tls_server_advertises_both_protocols_by_default() {
1354 let (harness, address, cert_chain) = start_tls_server("http-alpn-auto", false).await;
1355
1356 let negotiated = negotiate_alpn(address, cert_chain.clone(), &[b"h2", b"http/1.1"])
1358 .await
1359 .expect("handshake should succeed");
1360 assert_eq!(negotiated, Some(b"h2".to_vec()));
1361
1362 let negotiated = negotiate_alpn(address, cert_chain, &[b"http/1.1"])
1363 .await
1364 .expect("handshake should succeed");
1365 assert_eq!(negotiated, Some(b"http/1.1".to_vec()));
1366
1367 harness.shutdown().await;
1368 }
1369
1370 #[tokio::test]
1371 async fn tls_server_advertises_only_http2_when_restricted() {
1372 let (harness, address, cert_chain) = start_tls_server("http-alpn-http2-only", true).await;
1373
1374 let negotiated = negotiate_alpn(address, cert_chain.clone(), &[b"h2", b"http/1.1"])
1375 .await
1376 .expect("handshake should succeed");
1377 assert_eq!(negotiated, Some(b"h2".to_vec()));
1378
1379 negotiate_alpn(address, cert_chain, &[b"http/1.1"])
1387 .await
1388 .expect_err("handshake should fail when no offered protocol is served");
1389
1390 harness.shutdown().await;
1391 }
1392
1393 #[tokio::test]
1394 async fn a_stalled_tls_handshake_does_not_block_other_connections() {
1395 let (harness, address, cert_chain) = start_tls_server("http-tls-head-of-line", false).await;
1399
1400 let _stalled = TcpStream::connect(address).await.expect("should connect");
1401
1402 let negotiated = timeout(TEST_TIMEOUT, negotiate_alpn(address, cert_chain, &[b"h2"]))
1403 .await
1404 .expect("a stalled handshake must not delay another client's")
1405 .expect("handshake should succeed");
1406 assert_eq!(negotiated, Some(b"h2".to_vec()));
1407
1408 harness.shutdown().await;
1411 }
1412
1413 fn thread_recording_server(listen_address: ListenAddress) -> (HttpServer, Arc<Mutex<Option<String>>>) {
1415 let thread_name = Arc::new(Mutex::new(None));
1416 let recorder = Arc::clone(&thread_name);
1417
1418 let server = server_with(listen_address, move || {
1419 let recorder = Arc::clone(&recorder);
1420 async move {
1421 *recorder.lock().unwrap() = Some(std::thread::current().name().unwrap_or_default().to_string());
1422 ok_response().await
1423 }
1424 });
1425
1426 (server, thread_name)
1427 }
1428
1429 fn named_pool(name: &'static str) -> tokio::runtime::Runtime {
1431 tokio::runtime::Builder::new_multi_thread()
1432 .worker_threads(1)
1433 .thread_name(name)
1434 .enable_all()
1435 .build()
1436 .expect("should build pool")
1437 }
1438
1439 #[tokio::test]
1440 async fn the_worker_pool_runs_request_handling() {
1441 let pool = named_pool("http-pool-test");
1446
1447 let (server, handler_thread) = thread_recording_server(ListenAddress::tcp_loopback(0));
1448 let server = RunningServer::start(server.with_worker_pool(pool.handle().clone())).await;
1449 let addr = server.bound_tcp_address().await;
1450
1451 let client = Client::builder(TokioExecutor::new()).build_http::<Empty<bytes::Bytes>>();
1452 let uri = format!("http://{addr}/").parse().expect("should be a valid URI");
1453 let response = timeout(TEST_TIMEOUT, client.get(uri))
1454 .await
1455 .expect("server should answer")
1456 .expect("request should succeed");
1457 assert_eq!(response.status(), StatusCode::OK);
1458
1459 let handler_thread = handler_thread.lock().unwrap().clone().expect("handler should have run");
1460 assert!(
1461 handler_thread.starts_with("http-pool-test"),
1462 "request handling must run on the worker pool, but ran on thread {handler_thread:?}"
1463 );
1464
1465 server.shutdown().await;
1466 pool.shutdown_background();
1467 }
1468
1469 #[tokio::test]
1470 async fn the_worker_pool_runs_request_handling_behind_tls() {
1471 let _ = saluki_tls::initialize_default_crypto_provider();
1474 let cert = SelfSignedCert::localhost();
1475 let tls_config = server_tls_config(&cert);
1476 let pool = named_pool("http-tls-pool-test");
1477
1478 let (server, handler_thread) = thread_recording_server(ListenAddress::tcp_loopback(0));
1479 let server = RunningServer::start(
1480 server
1481 .with_tls_config(tls_config)
1482 .with_worker_pool(pool.handle().clone()),
1483 )
1484 .await;
1485 let addr = server.bound_tcp_address().await;
1486
1487 let negotiated = timeout(TEST_TIMEOUT, negotiate_alpn(addr, cert.cert_chain(), &[b"http/1.1"]))
1488 .await
1489 .expect("handshake should complete")
1490 .expect("handshake should succeed");
1491 assert_eq!(negotiated, Some(b"http/1.1".to_vec()));
1492
1493 let mut roots = rustls::RootCertStore::empty();
1495 for cert in cert.cert_chain() {
1496 roots.add(cert).expect("should trust the self-signed cert");
1497 }
1498 let mut client_config = rustls::ClientConfig::builder()
1499 .with_root_certificates(roots)
1500 .with_no_client_auth();
1501 client_config.alpn_protocols = vec![b"http/1.1".to_vec()];
1502
1503 let stream = TcpStream::connect(addr).await.expect("should connect");
1504 let server_name = rustls::pki_types::ServerName::try_from("localhost").expect("should be a valid server name");
1505 let mut tls_stream = tokio_rustls::TlsConnector::from(Arc::new(client_config))
1506 .connect(server_name, stream)
1507 .await
1508 .expect("handshake should succeed");
1509 tls_stream
1510 .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
1511 .await
1512 .expect("should write request");
1513
1514 let mut response = Vec::new();
1515 timeout(TEST_TIMEOUT, tls_stream.read_to_end(&mut response))
1516 .await
1517 .expect("response should arrive")
1518 .expect("should read response");
1519 assert!(
1520 response.ends_with(b"ok"),
1521 "expected the request to be served, got {:?}",
1522 String::from_utf8_lossy(&response)
1523 );
1524
1525 let handler_thread = handler_thread.lock().unwrap().clone().expect("handler should have run");
1526 assert!(
1527 handler_thread.starts_with("http-tls-pool-test"),
1528 "request handling must run on the worker pool, but ran on thread {handler_thread:?}"
1529 );
1530
1531 server.shutdown().await;
1532 pool.shutdown_background();
1533 }
1534
1535 #[tokio::test]
1536 async fn publishes_bound_tcp_address_with_tls() {
1537 let _ = saluki_tls::initialize_default_crypto_provider();
1538 let cert = SelfSignedCert::localhost();
1539 let tls_config = ServerConfig::builder()
1540 .with_no_client_auth()
1541 .with_single_cert(cert.cert_chain(), cert.private_key())
1542 .expect("should build TLS config");
1543
1544 let harness = ServerTestHarness::start("http-tcp-tls-bound-address", move |supervisor, server_id| {
1545 let server = server_with(ListenAddress::tcp_loopback(0), ok_response)
1546 .with_tls_config(tls_config)
1547 .with_server_id(server_id);
1548 supervisor.add_worker(server.into_supervisor());
1549 })
1550 .await;
1551
1552 let local_tcp_address = match harness.bound_address().await {
1553 BoundListenAddress::Tcp(addr) => addr,
1554 other_addr => panic!("expected TCP address, got {:?}", other_addr),
1555 };
1556
1557 assert_ne!(local_tcp_address.port(), 0);
1558
1559 let stream = connect_tcp(local_tcp_address).await;
1564 drop(stream);
1565
1566 harness.shutdown().await;
1567 }
1568
1569 #[cfg(unix)]
1570 #[tokio::test]
1571 async fn publishes_bound_unix_address_with_tls() {
1572 let _ = saluki_tls::initialize_default_crypto_provider();
1573 let cert = SelfSignedCert::localhost();
1574 let tls_config = ServerConfig::builder()
1575 .with_no_client_auth()
1576 .with_single_cert(cert.cert_chain(), cert.private_key())
1577 .expect("should build TLS config");
1578
1579 let tempdir = tempfile::tempdir().expect("should create temp dir");
1580 let socket_path = tempdir.path().join("http.sock");
1581 let listen_address = ListenAddress::Unix(socket_path.clone());
1582
1583 let harness = ServerTestHarness::start("http-unix-tls-bound-address", move |supervisor, server_id| {
1584 let server = server_with(listen_address, ok_response)
1585 .with_tls_config(tls_config)
1586 .with_server_id(server_id);
1587 supervisor.add_worker(server.into_supervisor());
1588 })
1589 .await;
1590
1591 let local_unix_address = match harness.bound_address().await {
1592 BoundListenAddress::Unix(addr) => addr,
1593 other_addr => panic!("expected UDS address, got {:?}", other_addr),
1594 };
1595
1596 assert_eq!(socket_path, local_unix_address);
1597
1598 let stream = connect_unix(&socket_path).await;
1603 drop(stream);
1604
1605 harness.shutdown().await;
1606 }
1607
1608 #[tokio::test]
1609 async fn binds_its_listener_during_initialization() {
1610 let server = RunningServer::start(server_with(ListenAddress::tcp_loopback(0), ok_response)).await;
1614
1615 server.bound_tcp_address().await;
1616
1617 server.shutdown().await;
1618 }
1619
1620 #[tokio::test]
1621 async fn bind_failure_is_an_initialization_error() {
1622 let addr = free_local_addr();
1627 let _held = StdTcpListener::bind(addr).expect("should hold the address");
1628
1629 let server = RunningServer::start(server_with(ListenAddress::Tcp(addr), ok_response)).await;
1630
1631 match server.join().await {
1633 Ok(()) => panic!("the subtree should have failed to bind {addr}"),
1634 Err(SupervisorError::FailedToInitialize { source, .. }) => {
1635 let error = source.to_string();
1636 assert!(error.contains("Failed to bind listener"), "unexpected error: {error}");
1637 }
1638 Err(e) => panic!("expected an initialization failure, got {e:?}"),
1639 }
1640 }
1641
1642 #[tokio::test]
1643 async fn releases_its_port_once_the_subtree_finishes() {
1644 let server = RunningServer::start(server_with(ListenAddress::tcp_loopback(0), ok_response)).await;
1647 let addr = server.bound_tcp_address().await;
1648
1649 server.shutdown().await;
1650
1651 assert!(
1652 StdTcpListener::bind(addr).is_ok(),
1653 "the server should have released {addr} when its subtree finished"
1654 );
1655 }
1656
1657 #[tokio::test]
1658 async fn a_half_sent_request_does_not_wedge_the_drain() {
1659 let server = RunningServer::start(
1667 server_with(ListenAddress::tcp_loopback(0), ok_response)
1668 .with_graceful_shutdown_timeout(Duration::from_secs(1)),
1669 )
1670 .await;
1671 let addr = server.bound_tcp_address().await;
1672
1673 let mut stream = TcpStream::connect(addr).await.expect("should connect");
1674 stream
1675 .write_all(b"GET / HTTP/1.1\r\nHost: localhost")
1676 .await
1677 .expect("should write a partial request head");
1678 stream.flush().await.expect("should flush");
1679
1680 tokio::time::sleep(Duration::from_millis(100)).await;
1682
1683 server.shutdown().await;
1684 }
1685
1686 #[tokio::test]
1687 async fn does_not_finish_until_in_flight_requests_do() {
1688 let handler_started = Arc::new(AtomicBool::new(false));
1692 let started = Arc::clone(&handler_started);
1693 let mut server = RunningServer::start(server_with(ListenAddress::tcp_loopback(0), move || {
1694 let started = Arc::clone(&started);
1695 async move {
1696 started.store(true, Ordering::SeqCst);
1697 tokio::time::sleep(Duration::from_millis(300)).await;
1698 ok_response().await
1699 }
1700 }))
1701 .await;
1702 let addr = server.bound_tcp_address().await;
1703
1704 let mut stream = TcpStream::connect(addr).await.expect("should connect");
1706 stream
1707 .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
1708 .await
1709 .expect("should write request");
1710
1711 while !handler_started.load(Ordering::SeqCst) {
1712 tokio::time::sleep(Duration::from_millis(5)).await;
1713 }
1714
1715 server.signal_shutdown();
1717
1718 assert!(
1721 timeout(Duration::from_millis(50), &mut server.task).await.is_err(),
1722 "subtree should not finish while a request is still being served"
1723 );
1724
1725 let mut response = Vec::new();
1726 timeout(TEST_TIMEOUT, stream.read_to_end(&mut response))
1727 .await
1728 .expect("response should arrive")
1729 .expect("should read response");
1730 assert!(
1731 response.ends_with(b"ok"),
1732 "expected the in-flight response to complete, got {:?}",
1733 String::from_utf8_lossy(&response)
1734 );
1735
1736 let result = server.join().await;
1737 assert!(result.is_ok(), "subtree should stop cleanly after draining: {result:?}");
1738 }
1739
1740 #[tokio::test]
1741 async fn serves_http2_requests() {
1742 let harness = ServerTestHarness::start("http2-request", |supervisor, server_id| {
1746 let server = server_with(ListenAddress::tcp_loopback(0), ok_response).with_server_id(server_id);
1747 supervisor.add_worker(server.into_supervisor());
1748 })
1749 .await;
1750
1751 let address = harness.bound_tcp_address().await;
1752 let client = Client::builder(TokioExecutor::new())
1753 .http2_only(true)
1754 .build_http::<Empty<bytes::Bytes>>();
1755 let uri = format!("http://{address}/").parse().expect("should be a valid URI");
1756 let response = timeout(TEST_TIMEOUT, client.get(uri))
1757 .await
1758 .expect("server should answer an HTTP/2 request")
1759 .expect("request should succeed");
1760
1761 assert_eq!(response.version(), Version::HTTP_2);
1762 assert_eq!(response.status(), StatusCode::OK);
1763
1764 harness.shutdown().await;
1765 }
1766
1767 #[tokio::test]
1768 async fn an_http2_request_runs_as_a_supervised_child() {
1769 let recorder = TestRecorder::default();
1774 let _guard = metrics::set_default_local_recorder(&recorder);
1775
1776 let server = RunningServer::start(
1778 server_with(ListenAddress::tcp_loopback(0), ok_response).with_server_id("supervised-stream"),
1779 )
1780 .await;
1781 let addr = server.bound_tcp_address().await;
1782
1783 let client = Client::builder(TokioExecutor::new())
1784 .http2_only(true)
1785 .build_http::<Empty<bytes::Bytes>>();
1786 let uri = format!("http://{addr}/").parse().expect("should be a valid URI");
1787 let response = timeout(TEST_TIMEOUT, client.get(uri))
1788 .await
1789 .expect("server should answer an HTTP/2 request")
1790 .expect("request should succeed");
1791 assert_eq!(response.status(), StatusCode::OK);
1792
1793 server.shutdown().await;
1794
1795 let polls = recorder.counter((
1796 "runtime_task_poll_count",
1797 &[("task_name", "http_server_supervised_stream.http_conn_task")],
1798 ));
1799 assert!(
1800 polls.is_some_and(|polls| polls > 0),
1801 "the stream `hyper` executed should have run as a supervised child, got {polls:?}"
1802 );
1803 }
1804
1805 #[tokio::test]
1806 async fn http2_only_server_rejects_http1_requests() {
1807 let server =
1810 RunningServer::start(server_with(ListenAddress::tcp_loopback(0), ok_response).with_http2_only()).await;
1811 let addr = server.bound_tcp_address().await;
1812
1813 let mut stream = TcpStream::connect(addr).await.expect("should connect");
1814 stream
1815 .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
1816 .await
1817 .expect("should write request");
1818
1819 let mut response = Vec::new();
1823 let _ = timeout(TEST_TIMEOUT, stream.read_to_end(&mut response))
1824 .await
1825 .expect("server should close the connection rather than leaving the client hanging");
1826 assert!(
1827 !response.starts_with(b"HTTP/1.1"),
1828 "expected no HTTP/1.1 response, got {:?}",
1829 String::from_utf8_lossy(&response)
1830 );
1831
1832 server.shutdown().await;
1833 }
1834
1835 #[tokio::test]
1836 async fn an_idle_http2_peer_does_not_wedge_the_drain() {
1837 let server = RunningServer::start(
1840 server_with(ListenAddress::tcp_loopback(0), ok_response)
1841 .with_graceful_shutdown_timeout(Duration::from_secs(1)),
1842 )
1843 .await;
1844 let addr = server.bound_tcp_address().await;
1845
1846 let mut stream = TcpStream::connect(addr).await.expect("should connect");
1847 stream
1848 .write_all(b"PRI * HTTP/2.0\r\n")
1849 .await
1850 .expect("should write a partial HTTP/2 preface");
1851 stream.flush().await.expect("should flush");
1852 tokio::time::sleep(Duration::from_millis(100)).await;
1853
1854 server.shutdown().await;
1855 }
1856
1857 #[tokio::test]
1858 async fn retires_connections_that_reach_their_maximum_age() {
1859 let max_age = Duration::from_millis(300);
1862 let server = RunningServer::start(
1863 server_with(ListenAddress::tcp_loopback(0), ok_response)
1864 .with_http2_config(Http2Config::default().with_max_connection_age(max_age, None)),
1865 )
1866 .await;
1867 let addr = server.bound_tcp_address().await;
1868
1869 let started = Instant::now();
1872 let mut stream = TcpStream::connect(addr).await.expect("should connect");
1873 stream
1874 .write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
1875 .await
1876 .expect("should write request");
1877
1878 let mut response = Vec::new();
1879 timeout(TEST_TIMEOUT, stream.read_to_end(&mut response))
1880 .await
1881 .expect("server should close the connection once it reaches its maximum age")
1882 .expect("should read the response");
1883
1884 assert!(
1885 response.ends_with(b"ok"),
1886 "expected the request to be served before the connection was retired, got {:?}",
1887 String::from_utf8_lossy(&response)
1888 );
1889 assert!(
1890 started.elapsed() >= max_age,
1891 "connection was closed after {:?}, before reaching its maximum age of {:?}",
1892 started.elapsed(),
1893 max_age
1894 );
1895
1896 server.shutdown().await;
1897 }
1898}