torrust_index/web/api/server/
mod.rs

1pub mod custom_axum;
2pub mod signals;
3pub mod v1;
4
5use std::net::SocketAddr;
6use std::panic::Location;
7use std::sync::Arc;
8
9use axum_server::tls_rustls::RustlsConfig;
10use axum_server::Handle;
11use thiserror::Error;
12use tokio::sync::oneshot::{Receiver, Sender};
13use torrust_index_located_error::LocatedError;
14use tracing::{error, info};
15use v1::routes::router;
16
17use self::signals::{Halted, Started};
18use super::Running;
19use crate::common::AppData;
20use crate::config::Tsl;
21use crate::web::api::server::custom_axum::TimeoutAcceptor;
22use crate::web::api::server::signals::graceful_shutdown;
23
24pub type DynError = Arc<dyn std::error::Error + Send + Sync>;
25
26/// Starts the API server.
27///
28/// # Panics
29///
30/// Panics if the API server can't be started.
31pub async fn start(app_data: Arc<AppData>, config_bind_address: SocketAddr, opt_tsl: Option<Tsl>) -> Running {
32    let opt_rust_tls_config = make_rust_tls(&opt_tsl)
33        .await
34        .map(|tls| tls.expect("it should have a valid net tls configuration"));
35
36    let (tx_start, rx) = tokio::sync::oneshot::channel::<Started>();
37    let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::<Halted>();
38
39    // Run the API server
40    let join_handle = tokio::spawn(async move {
41        info!("Starting API server with net config: {} ...", config_bind_address);
42
43        start_server(config_bind_address, app_data.clone(), tx_start, rx_halt, opt_rust_tls_config).await;
44
45        info!("API server stopped");
46
47        Ok(())
48    });
49
50    // Wait until the API server is running
51    let bound_addr = match rx.await {
52        Ok(started) => started.socket_addr,
53        Err(err) => {
54            let msg = format!("Unable to start API server: {err}");
55            error!("{}", msg);
56            panic!("{}", msg);
57        }
58    };
59
60    Running {
61        socket_addr: bound_addr,
62        halt_task: tx_halt,
63        task: join_handle,
64    }
65}
66
67async fn start_server(
68    config_socket_addr: SocketAddr,
69    app_data: Arc<AppData>,
70    tx_start: Sender<Started>,
71    rx_halt: Receiver<Halted>,
72    rust_tls_config: Option<RustlsConfig>,
73) {
74    let router = router(app_data);
75    let socket = std::net::TcpListener::bind(config_socket_addr).expect("Could not bind tcp_listener to address.");
76    let address = socket.local_addr().expect("Could not get local_addr from tcp_listener.");
77
78    let handle = Handle::new();
79
80    tokio::task::spawn(graceful_shutdown(
81        handle.clone(),
82        rx_halt,
83        format!("Shutting down API server on socket address: {address}"),
84    ));
85
86    let tls = rust_tls_config.clone();
87    let protocol = if tls.is_some() { "https" } else { "http" };
88
89    info!("API server listening on {}://{}", protocol, address); // # DevSkim: ignore DS137138
90
91    tx_start
92        .send(Started { socket_addr: address })
93        .expect("the API server should not be dropped");
94
95    match tls {
96        Some(tls) => custom_axum::from_tcp_rustls_with_timeouts(socket, tls)
97            .handle(handle)
98            // The TimeoutAcceptor is commented because TSL does not work with it.
99            // See: https://github.com/torrust/torrust-index/issues/204
100            //.acceptor(TimeoutAcceptor)
101            .serve(router.into_make_service_with_connect_info::<std::net::SocketAddr>())
102            .await
103            .expect("API server should be running"),
104        None => custom_axum::from_tcp_with_timeouts(socket)
105            .handle(handle)
106            .acceptor(TimeoutAcceptor)
107            .serve(router.into_make_service_with_connect_info::<std::net::SocketAddr>())
108            .await
109            .expect("API server should be running"),
110    };
111}
112
113#[derive(Error, Debug)]
114pub enum Error {
115    /// Enabled tls but missing config.
116    #[error("tls config missing")]
117    MissingTlsConfig { location: &'static Location<'static> },
118
119    /// Unable to parse tls Config.
120    #[error("bad tls config: {source}")]
121    BadTlsConfig {
122        source: LocatedError<'static, dyn std::error::Error + Send + Sync>,
123        ssl_cert_path: String,
124        ssl_key_path: String,
125    },
126}
127
128pub async fn make_rust_tls(tsl_config: &Option<Tsl>) -> Option<Result<RustlsConfig, Error>> {
129    if let Some(tsl) = tsl_config {
130        if let (Some(cert), Some(key)) = (tsl.ssl_cert_path.clone(), tsl.ssl_key_path.clone()) {
131            info!("Using https. Cert path: {cert}.");
132            info!("Using https. Key path: {key}.");
133
134            let ssl_cert_path = cert.clone().to_string();
135            let ssl_key_path = key.clone().to_string();
136
137            Some(
138                RustlsConfig::from_pem_file(cert, key)
139                    .await
140                    .map_err(|err| Error::BadTlsConfig {
141                        source: (Arc::new(err) as DynError).into(),
142                        ssl_cert_path,
143                        ssl_key_path,
144                    }),
145            )
146        } else {
147            Some(Err(Error::MissingTlsConfig {
148                location: Location::caller(),
149            }))
150        }
151    } else {
152        info!("TLS not enabled");
153        None
154    }
155}