use std::future::Future; use std::net::SocketAddr; use axum::{Router, serve}; use database::db::ServerPostgres; use eyre::{Context, Result}; pub(crate) mod database; mod handlers; mod metrics; mod router; pub(crate) use settings::Settings; pub(crate) mod settings; use tokio::net::TcpListener; use tokio::signal; #[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..."); } pub(crate) 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 } pub(crate) async fn launch_with_tcp_listener( settings: Settings, listener: TcpListener, shutdown: impl Future + 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. pub(crate) 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 { 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) }