saluki_io/net/server/
http.rs1use std::{
4 future::Future,
5 pin::Pin,
6 sync::Arc,
7 task::{ready, Context, Poll},
8};
9
10use http::{Request, Response};
11use http_body::Body;
12use hyper::{body::Incoming, service::Service};
13use hyper_util::{
14 rt::{TokioExecutor, TokioIo},
15 server::conn::auto::Builder,
16};
17use rustls::ServerConfig;
18use saluki_common::{
19 sync::shutdown::{ShutdownCoordinator, ShutdownHandle},
20 task::{spawn_traced_named, HandleExt as _},
21};
22use saluki_error::GenericError;
23use saluki_tls::ensure_server_config_fips_compliant;
24use tokio::{pin, runtime::Handle, select, sync::oneshot};
25use tokio_rustls::TlsAcceptor;
26use tracing::{debug, error, info};
27
28use crate::net::listener::ConnectionOrientedListener;
29
30pub struct HttpServer<S> {
32 executor: Handle,
33 listener: ConnectionOrientedListener,
34 conn_builder: Builder<TokioExecutor>,
35 service: S,
36 tls_config: Option<ServerConfig>,
37}
38
39impl<S> HttpServer<S> {
40 pub fn from_listener(listener: ConnectionOrientedListener, service: S) -> Self {
46 Self {
47 executor: Handle::current(),
48 listener,
49 conn_builder: Builder::new(TokioExecutor::new()),
50 service,
51 tls_config: None,
52 }
53 }
54
55 pub fn with_tls_config(mut self, config: ServerConfig) -> Self {
61 self.tls_config = Some(config);
62 self
63 }
64
65 pub fn with_executor(mut self, executor: Handle) -> Self {
72 self.executor = executor;
73 self
74 }
75}
76
77impl<S, B> HttpServer<S>
78where
79 S: Service<Request<Incoming>, Response = Response<B>> + Send + Clone + 'static,
80 S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
81 S::Future: Send + 'static,
82 B: Body + Send + 'static,
83 B::Data: Send,
84 B::Error: std::error::Error + Send + Sync,
85{
86 pub fn listen(self) -> (ShutdownCoordinator, ErrorHandle) {
91 let (shutdown_coordinator, shutdown) = ShutdownHandle::paired();
92 let (error_tx, error_rx) = oneshot::channel();
93
94 let Self {
95 executor,
96 mut listener,
97 conn_builder,
98 service,
99 tls_config,
100 ..
101 } = self;
102
103 spawn_traced_named("http-server-acceptor", async move {
104 let maybe_tls_acceptor = match tls_config {
105 Some(mut config) => {
106 config.alpn_protocols.push(b"h2".to_vec());
108 config.alpn_protocols.push(b"http/1.1".to_vec());
109
110 if let Err(e) = ensure_server_config_fips_compliant(&mut config) {
111 let _ = error_tx.send(e);
112 return;
113 }
114
115 Some(TlsAcceptor::from(Arc::new(config)))
116 }
117 None => None,
118 };
119 let tls_enabled = maybe_tls_acceptor.is_some();
120
121 info!(listen_addr = %listener.listen_address(), tls_enabled, "HTTP server started.");
122
123 pin!(shutdown);
124
125 loop {
126 select! {
127 result = listener.accept() => match result {
128 Ok(stream) => {
129 let service = service.clone();
130 let conn_builder = conn_builder.clone();
131 let listen_addr = listener.listen_address().clone();
132 match &maybe_tls_acceptor {
133 Some(acceptor) => {
134 let tls_stream = match acceptor.accept(stream).await {
135 Ok(stream) => stream,
136 Err(e) => {
137 error!(%listen_addr, error = %e, "Failed to complete TLS handshake.");
138 continue
139 },
140 };
141
142 executor.spawn_traced_named("http-server-tls-conn-handler", async move {
143 if let Err(e) = conn_builder.serve_connection(TokioIo::new(tls_stream), service).await {
144 error!(%listen_addr, error = %e, "Failed to serve HTTP connection.");
145 }
146 });
147 },
148 None => {
149 executor.spawn_traced_named("http-server-conn-handler", async move {
150 if let Err(e) = conn_builder.serve_connection(TokioIo::new(stream), service).await {
151 error!(%listen_addr, error = %e, "Failed to serve HTTP connection.");
152 }
153 });
154 },
155 }
156 },
157 Err(e) => {
158 let _ = error_tx.send(e.into());
159 break;
160 }
161 },
162
163 _ = &mut shutdown => {
164 debug!(listen_addr = %listener.listen_address(), "Received shutdown signal.");
165 break;
166 }
167 }
168 }
169
170 info!(listen_addr = %listener.listen_address(), "HTTP server stopped.");
171 });
172
173 (shutdown_coordinator, ErrorHandle(error_rx))
174 }
175}
176
177pub struct ErrorHandle(oneshot::Receiver<GenericError>);
179
180impl Future for ErrorHandle {
181 type Output = Option<GenericError>;
182
183 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
184 match ready!(Pin::new(&mut self.0).poll(cx)) {
185 Ok(err) => Poll::Ready(Some(err)),
186 Err(_) => Poll::Ready(None),
187 }
188 }
189}