slim_config/grpc/
server.rs1pub use crate::server::{AuthenticationConfig, KeepaliveServerParameters, ServerConfig};
5
6use std::convert::Infallible;
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10use std::{net::SocketAddr, str::FromStr};
11
12use display_error_chain::ErrorChainExt;
13use futures::FutureExt;
14use futures::Stream;
15use tokio::io::{AsyncRead, AsyncWrite};
16use tokio_util::sync::CancellationToken;
17use tonic::service::Routes;
18use tonic::transport::server::TcpIncoming;
19use tower_http::BoxError;
20use tracing::debug;
21
22#[cfg(target_family = "unix")]
23use {std::path::PathBuf, tokio::net::UnixListener, tokio_stream::wrappers::UnixListenerStream};
24
25use crate::auth::ServerAuthenticator;
26use crate::auth::jwt::Config as JwtAuthenticationConfig;
27use crate::auth::oidc::Config as OidcConfig;
28#[cfg(not(target_family = "windows"))]
29use crate::auth::spire::SpireConfig as SpireAuthConfig;
30use crate::errors::ConfigError;
31use crate::tls::common::RustlsConfigLoader;
32use crate::transport::TransportProtocol;
33
34pub type ServerFuture = Pin<Box<dyn Future<Output = Result<(), tonic::transport::Error>> + Send>>;
36
37fn routes_from_slice<S>(svc: &[S]) -> Routes
38where
39 S: tower_service::Service<
40 http::Request<tonic::body::Body>,
41 Response = http::Response<tonic::body::Body>,
42 Error = Infallible,
43 > + tonic::server::NamedService
44 + Clone
45 + Send
46 + Sync
47 + 'static,
48 S::Future: Send + 'static,
49{
50 let mut routes = Routes::new(svc[0].clone());
51 for s in svc.iter().skip(1) {
52 routes = routes.add_service(s.clone());
53 }
54 routes
55}
56
57impl ServerConfig {
58 pub async fn to_server_future<S>(&self, svc: &[S]) -> Result<ServerFuture, ConfigError>
63 where
64 S: tower_service::Service<
65 http::Request<tonic::body::Body>,
66 Response = http::Response<tonic::body::Body>,
67 Error = Infallible,
68 >
69 + tonic::server::NamedService
70 + Clone
71 + Send
72 + 'static
73 + Sync,
74 S::Future: Send + 'static,
75 {
76 if svc.is_empty() {
77 return Err(ConfigError::MissingServices);
78 }
79 self.to_server_future_with_routes(routes_from_slice(svc))
80 .await
81 }
82
83 pub async fn to_server_future_with_routes(
87 &self,
88 routes: Routes,
89 ) -> Result<ServerFuture, ConfigError> {
90 if self.resolved_transport() == TransportProtocol::Websocket {
91 return Err(ConfigError::GrpcServerUnsupportedTransport);
92 }
93
94 if self.endpoint.is_empty() {
95 return Err(ConfigError::MissingEndpoint);
96 }
97
98 #[cfg(target_family = "unix")]
99 if self.endpoint.starts_with("unix://") {
100 if !self.tls_setting.insecure {
101 return Err(ConfigError::UnixSocketTlsUnsupported);
103 }
104
105 let socket_path = parse_unix_socket_path(self.endpoint.as_str())?;
106
107 let _ = std::fs::remove_file(&socket_path);
109
110 let listener = UnixListener::bind(&socket_path)?;
111 let incoming = UnixListenerStream::new(listener);
112
113 return self.serve_with_incoming(routes, incoming).await;
114 }
115
116 #[cfg(not(target_family = "unix"))]
117 if self.endpoint.starts_with("unix://") {
118 return Err(ConfigError::UnixSocketUnsupported);
119 }
120
121 let addr = SocketAddr::from_str(self.endpoint.as_str())?;
122
123 let tls_config = self.tls_setting.load_rustls_config().await?;
125 let incoming = TcpIncoming::bind(addr)?;
126
127 match tls_config {
128 Some(tls_config) => {
129 let incoming = tonic_tls::rustls::TlsIncoming::new(incoming, Arc::new(tls_config));
130 self.serve_with_incoming(routes, incoming).await
131 }
132 None => self.serve_with_incoming(routes, incoming).await,
133 }
134 }
135
136 pub async fn run_grpc_server<S>(
144 &self,
145 svc: &[S],
146 drain_rx: drain::Watch,
147 ) -> Result<CancellationToken, ConfigError>
148 where
149 S: tower_service::Service<
150 http::Request<tonic::body::Body>,
151 Response = http::Response<tonic::body::Body>,
152 Error = Infallible,
153 >
154 + tonic::server::NamedService
155 + Clone
156 + Send
157 + 'static
158 + Sync,
159 S::Future: Send + 'static,
160 {
161 if svc.is_empty() {
162 return Err(ConfigError::MissingServices);
163 }
164 self.run_grpc_server_with_routes(routes_from_slice(svc), drain_rx)
165 .await
166 }
167
168 pub async fn run_grpc_server_with_routes(
171 &self,
172 routes: Routes,
173 drain_rx: drain::Watch,
174 ) -> Result<CancellationToken, ConfigError> {
175 debug!(%self, "server configured: setting it up");
176 let server_future = self.to_server_future_with_routes(routes).await?;
177
178 let token = CancellationToken::new();
180 let token_clone = token.clone();
181
182 tokio::spawn(async move {
184 debug!("starting server main loop");
185 let shutdown = drain_rx.signaled();
186
187 tokio::select! {
188 res = server_future => {
189 match res {
190 Ok(_) => {
191 debug!("server shutdown");
192 }
193 Err(e) => {
194 tracing::error!(error = %e.chain(), "server error");
195 }
196 }
197 }
198 _ = shutdown => {
199 debug!("shutting down server");
200 }
201 _ = token.cancelled() => {
202 debug!("cancellation token triggered: shutting down server");
203 }
204 }
205 });
206
207 Ok(token_clone)
208 }
209
210 fn create_server_builder(&self) -> tonic::transport::Server {
211 let builder: tonic::transport::Server =
212 tonic::transport::Server::builder().accept_http1(false);
213
214 let builder = match self.max_concurrent_streams {
215 Some(max_concurrent_streams) => {
216 builder.concurrency_limit_per_connection(max_concurrent_streams as usize)
217 }
218 None => builder,
219 };
220
221 let builder = match self.max_frame_size {
222 Some(max_frame_size) => builder.max_frame_size(max_frame_size * 1024 * 1024),
223 None => builder,
224 };
225
226 let builder = match self.max_header_list_size {
227 Some(max_header_list_size) => builder.http2_max_header_list_size(max_header_list_size),
228 None => builder,
229 };
230
231 let builder = builder.http2_keepalive_interval(Some(self.keepalive.time.into()));
232 let builder = builder.http2_keepalive_timeout(Some(self.keepalive.timeout.into()));
233
234 builder.max_connection_age(self.keepalive.max_connection_age.into())
235 }
236
237 async fn serve_with_incoming<I, IO, IE>(
238 &self,
239 routes: Routes,
240 incoming: I,
241 ) -> Result<ServerFuture, ConfigError>
242 where
243 I: Stream<Item = Result<IO, IE>> + Send + 'static,
244 IO: AsyncRead + AsyncWrite + tonic::transport::server::Connected + Unpin + Send + 'static,
245 IE: Into<BoxError> + Send + 'static,
246 {
247 let mut builder = self.create_server_builder();
248
249 match &self.auth {
250 AuthenticationConfig::Basic(basic) => {
251 let auth_layer = basic.get_server_layer()?;
252 let router = builder.layer(auth_layer).add_routes(routes);
253 Ok(router.serve_with_incoming(incoming).boxed())
254 }
255 AuthenticationConfig::Jwt(jwt) => {
256 let auth_layer = <JwtAuthenticationConfig as ServerAuthenticator<
257 http::Response<tonic::body::Body>,
258 >>::get_server_layer(jwt)?;
259
260 let router = builder.layer(auth_layer).add_routes(routes);
261 Ok(router.serve_with_incoming(incoming).boxed())
262 }
263 #[cfg(not(target_family = "windows"))]
264 AuthenticationConfig::Spire(spire) => {
265 let mut auth_layer = <SpireAuthConfig as ServerAuthenticator<
266 http::Response<tonic::body::Body>,
267 >>::get_server_layer(spire)?;
268
269 auth_layer.initialize().await?;
270
271 let router = builder.layer(auth_layer).add_routes(routes);
272 Ok(router.serve_with_incoming(incoming).boxed())
273 }
274 AuthenticationConfig::Oidc(oidc) => {
275 let auth_layer = <OidcConfig as ServerAuthenticator<
277 http::Response<tonic::body::Body>,
278 >>::get_server_layer(oidc)?;
279 let router = builder.layer(auth_layer).add_routes(routes);
280 Ok(router.serve_with_incoming(incoming).boxed())
281 }
282 AuthenticationConfig::None => {
283 let router = builder.add_routes(routes);
284 Ok(router.serve_with_incoming(incoming).boxed())
285 }
286 }
287 }
288}
289
290#[cfg(target_family = "unix")]
291fn parse_unix_socket_path(endpoint: &str) -> Result<PathBuf, ConfigError> {
292 let path = endpoint.strip_prefix("unix://").unwrap_or(endpoint);
293
294 let without_query = path.split_once('?').map(|(p, _)| p).unwrap_or(path);
295 let path_part = without_query
296 .split_once('#')
297 .map(|(p, _)| p)
298 .unwrap_or(without_query);
299
300 if path_part.is_empty() {
301 return Err(ConfigError::UnixSocketMissingPath);
302 }
303
304 Ok(PathBuf::from(path_part))
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use crate::testutils::{Empty, helloworld::greeter_server::GreeterServer};
311 use crate::tls::common::TlsSource;
312
313 static TEST_DATA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/testdata/grpc");
314
315 #[tokio::test]
316 async fn test_to_incoming_server_config() {
317 let mut server_config = ServerConfig::default();
318 let empty_service = Arc::new(Empty::new());
319
320 let ret = server_config
322 .to_server_future(&[GreeterServer::from_arc(empty_service.clone())])
323 .await;
324 assert!(ret.is_err_and(|e| { e.to_string().contains("missing grpc endpoint") }));
326
327 server_config.endpoint = "0.0.0.0:123456".to_string();
329 let ret = server_config
330 .to_server_future(&[GreeterServer::from_arc(empty_service.clone())])
331 .await;
332 assert!(ret.is_err_and(|e| { matches!(e, ConfigError::EndpointParse(_)) }));
333
334 server_config.endpoint = "0.0.0.0:12345".to_string();
336 let ret = server_config
337 .to_server_future(&[GreeterServer::from_arc(empty_service.clone())])
338 .await;
339 assert!(ret.is_err_and(|e| { matches!(e, ConfigError::TlsConfig(_)) }));
340
341 server_config.tls_setting.insecure = true;
343 let ret = server_config
344 .to_server_future(&[GreeterServer::from_arc(empty_service.clone())])
345 .await;
346 assert!(ret.is_ok());
347
348 drop(ret.unwrap());
350
351 server_config.tls_setting.insecure = false;
353 server_config.tls_setting.config.source = TlsSource::File {
354 cert: format!("{}/server.crt", TEST_DATA_PATH),
355 key: format!("{}/server.key", TEST_DATA_PATH),
356 };
357 let ret = server_config
358 .to_server_future(&[GreeterServer::from_arc(empty_service.clone())])
359 .await;
360 assert!(ret.is_ok());
361 }
362
363 #[tokio::test]
364 async fn test_to_server_future_rejects_websocket_transport() {
365 let empty_service = Arc::new(Empty::new());
366 let server_config = ServerConfig::with_endpoint("ws://0.0.0.0:12345");
367 let ret = server_config
368 .to_server_future(&[GreeterServer::from_arc(empty_service)])
369 .await;
370 assert!(matches!(
371 ret,
372 Err(ConfigError::GrpcServerUnsupportedTransport)
373 ));
374 }
375}