use axum::routing::get_service;
use reqwest::Url;
use std::net::SocketAddr;
use std::path::Path;
use tokio::sync::oneshot;
use tower_http::services::ServeDir;
pub struct StaticDirectoryServer {
local_addr: SocketAddr,
shutdown_sender: Option<oneshot::Sender<()>>,
}
impl StaticDirectoryServer {
pub fn url(&self) -> Url {
Url::parse(&format!("http://localhost:{}", self.local_addr.port())).unwrap()
}
}
impl StaticDirectoryServer {
pub async fn new(path: impl AsRef<Path>) -> Result<Self, StaticDirectoryServerError> {
let service = get_service(ServeDir::new(path));
let app = axum::Router::new().nest_service("/", service);
let addr = SocketAddr::new([127, 0, 0, 1].into(), 0);
let listener = tokio::net::TcpListener::bind(addr).await?;
let addr = listener.local_addr()?;
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
let _ = axum::serve(listener, app.into_make_service())
.with_graceful_shutdown(async {
rx.await.ok();
})
.await;
});
Ok(Self {
local_addr: addr,
shutdown_sender: Some(tx),
})
}
}
impl Drop for StaticDirectoryServer {
fn drop(&mut self) {
if let Some(tx) = self.shutdown_sender.take() {
let _ = tx.send(());
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum StaticDirectoryServerError {
#[error(transparent)]
Io(#[from] std::io::Error),
}