use std::fs; use std::path::PathBuf; use axum::body::Body; use axum::extract::State; use axum::http::Uri; use axum::response::{IntoResponse, Response}; use axum::Router; use axum::routing::get; use crate::renderer::{server_render, RenderOutput}; pub async fn start_server(root: PathBuf, port: u32) { let app = Router::new() .route("/", get(|State(root): State| async move { // We need a separate route for the default path because {*p} must match at least one thing serve_path("index.html".to_string(), root.clone()) })) .route("/{*path}", get(|State(root): State, uri: Uri| async move { serve_path(String::from(uri.path()), root.clone()) })) .with_state(root); let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await.unwrap(); axum::serve(listener, app).await.unwrap(); } fn serve_path(path: String, root: PathBuf) -> impl IntoResponse { let path = path.strip_prefix("/").unwrap_or(path.as_str()); let render = server_render(path.into(), root.as_ref()); match render { Ok(RenderOutput::Rendered(_, content)) | Ok(RenderOutput::Hidden(_, content)) => { // We rendered some output so spit it back Response::builder() .status(200) .body(Body::from(content)).unwrap() } Ok(RenderOutput::RawFile(file)) => { // We were pointed at the raw contents of a file: Response::builder() .status(200) .body(Body::from(fs::read(root.join(file)).unwrap_or(vec![]))).unwrap() } Ok(RenderOutput::NoOutput) | Ok(RenderOutput::Dir(_)) => { // Asked for something that doesn't exist: Response::builder() .status(404) .body(Body::from(include_str!("../templates/404.html"))).unwrap() } Err(err) => { // Oh no! Response::builder() .status(500) .body(Body::from(format!("{}", err))).unwrap() } } }