use super::{logging, shutdown};
use crate::{app_module, configs::Config};
use std::{future::IntoFuture, sync::Arc};
use tokio::sync::Notify;
pub async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
crate::configs::load_env()?;
logging::init()?;
let config = Config::from_env()?;
let app = app_module::AppModule::router()?.merge(super::docs::router(
&config,
app_module::AppModule::api_operations(),
)?);
let app = super::router(app, &config);
let listener = tokio::net::TcpListener::bind(config.bind_address).await?;
let address = listener.local_addr()?;
let url = local_url(address);
tracing::info!(address = %address, url = %url, "server listening");
if config.docs.enabled {
tracing::info!(url = %format!("{url}/docs/"), "Swagger UI available");
tracing::info!(url = %format!("{url}/docs/openapi.json"), "OpenAPI document available");
} else {
tracing::info!("Swagger documentation disabled (SWAGGER_ENABLED=false)");
}
let draining = Arc::new(Notify::new());
let shutdown_started = draining.clone();
let server = axum::serve(
listener,
app.into_make_service_with_connect_info::<std::net::SocketAddr>(),
)
.with_graceful_shutdown(async move {
shutdown::signal().await;
tracing::info!("shutdown requested; draining requests");
shutdown_started.notify_one();
})
.into_future();
tokio::pin!(server);
tokio::select! {
result = &mut server => result?,
_ = draining.notified() => {
match tokio::time::timeout(config.shutdown_grace, &mut server).await {
Ok(result) => result?,
Err(error) => {
tracing::error!("shutdown drain deadline exceeded");
return Err(error.into());
}
}
}
}
tracing::info!("server stopped");
Ok(())
}
fn local_url(mut address: std::net::SocketAddr) -> String {
if address.ip().is_unspecified() {
address.set_ip(if address.is_ipv4() {
std::net::Ipv4Addr::LOCALHOST.into()
} else {
std::net::Ipv6Addr::LOCALHOST.into()
});
}
format!("http://{address}")
}
#[cfg(test)]
mod tests {
#[test]
fn startup_urls_use_accessible_hosts_and_preserve_ports() {
for (address, expected) in [
("0.0.0.0:3000", "http://127.0.0.1:3000"),
("127.0.0.1:4100", "http://127.0.0.1:4100"),
("[::]:3000", "http://[::1]:3000"),
("[::1]:4100", "http://[::1]:4100"),
] {
assert_eq!(super::local_url(address.parse().unwrap()), expected);
}
}
}