use std::{io::ErrorKind, net::SocketAddr, path::PathBuf, sync::mpsc::Receiver, thread};
use anyhow::{Result, anyhow};
use axum::{Router, http};
use tokio::net::TcpListener;
use tower::layer::util::Stack;
use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer};
use tower_livereload::LiveReloadLayer;
type Srhl = SetResponseHeaderLayer<http::HeaderValue>;
fn no_cache_layer() -> Stack<Srhl, Stack<Srhl, Srhl>> {
Stack::new(
SetResponseHeaderLayer::overriding(
http::header::CACHE_CONTROL,
http::HeaderValue::from_static("no-cache, no-store, must-revalidate"),
),
Stack::new(
SetResponseHeaderLayer::overriding(
http::header::PRAGMA,
http::HeaderValue::from_static("no-cache"),
),
SetResponseHeaderLayer::overriding(
http::header::EXPIRES,
http::HeaderValue::from_static("0"),
),
),
)
}
pub async fn serve(reload_rx: Receiver<()>, path: PathBuf) {
if let Err(error) = try_serve(reload_rx, path).await {
eprintln!("{:?}", error);
std::process::exit(1);
}
}
async fn try_serve(reload_rx: Receiver<()>, path: PathBuf) -> Result<()> {
let livereload = LiveReloadLayer::new();
let reloader = livereload.reloader();
thread::spawn(move || {
for _reload_request in reload_rx {
reloader.reload();
}
});
let app = Router::new()
.fallback_service(ServeDir::new(path))
.layer(livereload)
.layer(no_cache_layer());
let mut listener: Option<TcpListener> = None;
let mut addr: Option<SocketAddr> = None;
let ports = 8000..8050;
for port in ports.clone() {
let candidate_addr: std::net::SocketAddr = ([0, 0, 0, 0], port).into();
match tokio::net::TcpListener::bind(candidate_addr).await {
Ok(candidate_listener) => {
listener = Some(candidate_listener);
addr = Some(candidate_addr);
break;
}
Err(e) => {
if e.kind() == ErrorKind::AddrInUse {
continue;
} else {
return Err(e.into());
}
}
}
}
let (Some(listener), Some(addr)) = (listener, addr) else {
return Err(anyhow!(
"Couldn't serve your website locally. We tried binding to ports {:?}",
ports
));
};
log::info!("serving your website locally at the link: http://{}/", addr);
axum::serve(listener, app).await?;
Ok(())
}