zero-to-axum/src/server/mod.rs
2025-05-14 13:13:00 -05:00

98 lines
2.2 KiB
Rust

pub mod routes;
use anyhow::Result;
use axum::extract::FromRef;
use axum_extra::extract::cookie::Key;
use pin_project::pin_project;
use std::future::{Future, IntoFuture};
use std::net::SocketAddr;
use std::pin::pin;
use std::pin::Pin;
use tokio::signal;
use tracing::info;
use crate::Conf;
#[pin_project]
pub struct ZeroToAxum {
#[pin]
server: Pin<Box<dyn Future<Output = Result<(), std::io::Error>> + Send>>,
bound_addr: SocketAddr,
}
impl Future for ZeroToAxum {
type Output = Result<(), std::io::Error>;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
self.project().server.poll(cx)
}
}
impl ZeroToAxum {
pub fn local_addr(&self) -> SocketAddr {
self.bound_addr
}
pub async fn serve(conf: Conf) -> ZeroToAxum {
let state = AppState {
// TODO: pull from config
key: Key::generate(),
};
let app = routes::build().with_state(state);
let listener = tokio::net::TcpListener::bind(&conf.listen).await.unwrap();
let bound_addr = listener.local_addr().unwrap();
let server = axum::serve(listener, app).with_graceful_shutdown(shutdown_signal());
info!("server started, listening on {bound_addr:?}");
ZeroToAxum {
server: Box::pin(server.into_future()),
bound_addr,
}
}
}
#[derive(Clone)]
pub struct AppState {
// The key used to encrypt cookies.
key: Key,
}
impl FromRef<AppState> for Key {
fn from_ref(state: &AppState) -> Self {
state.key.clone()
}
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
info!("got Ctrl+C, shutting down...");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
info!("got SIGTERM, shutting down...");
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}