diff options
Diffstat (limited to 'crates/server/src/main.rs')
| -rw-r--r-- | crates/server/src/main.rs | 88 |
1 files changed, 84 insertions, 4 deletions
diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index 94d6d143..fb587754 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -1,13 +1,25 @@ +use std::future::Future; use std::net::SocketAddr; -use turtle_server::{Settings, database::DbType, launch, launch_metrics_server}; - +use axum::{Router, serve}; use clap::Parser; +use database::db::ServerPostgres; use eyre::{Context, Result, eyre}; +use tokio::net::TcpListener; +use tokio::signal; + +use crate::database::DbType; +use crate::settings::Settings; + +mod database; +mod handlers; +mod metrics; +mod router; +mod settings; #[derive(Parser, Clone, Debug)] #[command(infer_subcommands = true)] -pub enum Cmd { +pub(crate) enum Cmd { /// Start the server Start { /// The host address to bind @@ -24,7 +36,7 @@ pub enum Cmd { } impl Cmd { - pub async fn run(self) -> Result<()> { + async fn run(self) -> Result<()> { match self { Self::Start { host, port } => { let settings = Settings::new().wrap_err("could not load server settings")?; @@ -59,3 +71,71 @@ impl Cmd { async fn main() -> Result<()> { Cmd::parse().run().await } + +#[cfg(target_family = "unix")] +async fn shutdown_signal() { + let mut term = signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to register signal handler"); + let mut interrupt = signal::unix::signal(signal::unix::SignalKind::interrupt()) + .expect("failed to register signal handler"); + + tokio::select! { + _ = term.recv() => {}, + _ = interrupt.recv() => {}, + }; + eprintln!("Shutting down gracefully..."); +} + +async fn launch(settings: Settings, addr: SocketAddr) -> Result<()> { + launch_with_tcp_listener( + settings, + TcpListener::bind(addr) + .await + .context("could not connect to socket")?, + shutdown_signal(), + ) + .await +} + +async fn launch_with_tcp_listener( + settings: Settings, + listener: TcpListener, + shutdown: impl Future<Output = ()> + Send + 'static, +) -> Result<()> { + let r = make_router(settings).await?; + + serve(listener, r.into_make_service()) + .with_graceful_shutdown(shutdown) + .await?; + + Ok(()) +} + +// The separate listener means it's much easier to ensure metrics are not accidentally exposed to +// the public. +async fn launch_metrics_server(host: String, port: u16) -> Result<()> { + let listener = TcpListener::bind((host, port)) + .await + .context("failed to bind metrics tcp")?; + + let recorder_handle = metrics::setup_metrics_recorder(); + + let router = Router::new().route( + "/metrics", + axum::routing::get(move || std::future::ready(recorder_handle.render())), + ); + + serve(listener, router.into_make_service()) + .with_graceful_shutdown(shutdown_signal()) + .await?; + + Ok(()) +} + +async fn make_router(settings: Settings) -> Result<Router, eyre::Error> { + let db = ServerPostgres::new(&settings.db_settings) + .await + .wrap_err_with(|| format!("failed to connect to db: {:?}", settings.db_settings))?; + let r = router::router(db, settings); + Ok(r) +} |
