diff options
| -rw-r--r-- | .gitattributes | 1 | ||||
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Cargo.toml | 14 | ||||
| -rw-r--r-- | public/index.html | 4 | ||||
| -rw-r--r-- | public/music.html | 4 | ||||
| -rw-r--r-- | src/env.rs | 20 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 91 | ||||
| -rw-r--r-- | src/state.rs | 18 |
9 files changed, 156 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5c1c87e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* linguist-language=HolyC diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e875dbc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "site" +version = "0.1.0" +edition = "2024" + +[dependencies] +axum = "0.8.9" +serde = "1.0.228" +serde_json = "1.0.150" +tokio = { version = "1.52.3", features = ["macros", "rt-multi-thread"] } +tower = "0.5.3" +tower-http = { version = "0.6.11", features = ["compression-gzip", "fs", "timeout", "trace"] } +tracing = "0.1.44" +tracing-subscriber = "0.3.23" diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..524959f --- /dev/null +++ b/public/index.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<html lang="en"> + <p>Hello and welcome</p> +</html> diff --git a/public/music.html b/public/music.html new file mode 100644 index 0000000..22ba402 --- /dev/null +++ b/public/music.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<html lang="en"> + <p>This is my music</p> +</html> diff --git a/src/env.rs b/src/env.rs new file mode 100644 index 0000000..0a4d46c --- /dev/null +++ b/src/env.rs @@ -0,0 +1,20 @@ +use std::{env, path::PathBuf}; + +pub struct Env { + pub site_addr: String, + pub dist_dir: PathBuf, +} + +impl Env { + pub fn get_or_default() -> Self { + let site_addr = env::var("SITE_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".to_string()); + let dist_dir = env::var("DIST_DIR") + .unwrap_or_else(|_| format!("{}/public", env!("CARGO_MANIFEST_DIR"))) + .into(); + + Self { + site_addr, + dist_dir, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c112ba4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod env; +pub mod state; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1ddddfb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +use axum::{ + Router, + extract::{Request, State}, + handler::HandlerWithoutStateExt, + http::StatusCode, + response::{Html, IntoResponse}, + routing::{get, get_service}, +}; +use site::{env::Env, state::AppState}; +use std::{path::PathBuf, time::Duration}; +use tokio::net::TcpListener; +use tower::util::ServiceExt; +use tower_http::{ + compression::CompressionLayer, + services::{ServeDir, ServeFile}, + timeout::TimeoutLayer, + trace::TraceLayer, +}; + +const ERR: (StatusCode, Html<&'static str>) = + (StatusCode::NOT_FOUND, Html("<h1>404 Not Found</h1>")); + +async fn not_found() -> impl IntoResponse { + ERR +} + +async fn static_files(State(state): State<AppState>, req: Request) -> impl IntoResponse { + ServeDir::new(state.get_dist_dir().as_ref()) + .precompressed_br() + .precompressed_gzip() + .fallback(not_found.into_service()) + .oneshot(req) + .await + .map_err(|_| ERR) +} + +async fn html_files(State(state): State<AppState>, mut req: Request) -> impl IntoResponse { + let mut file_path = PathBuf::from(req.uri().path()); + + if file_path.extension().is_none() { + file_path.set_extension("html"); + } + + *req.uri_mut() = file_path + .to_str() + // if this were to run on a windows machine + .map(|s| s.replace("\\", "/")) + .ok_or(ERR)? + .parse() + .map_err(|_| ERR)?; + + ServeDir::new(state.get_dist_dir().as_ref()) + .fallback(not_found.into_service()) + .oneshot(req) + .await + .map_err(|_| ERR) +} + +#[tokio::main] +async fn main() -> Result<(), Box<dyn std::error::Error>> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .init(); + + let Env { + site_addr, + dist_dir, + } = Env::get_or_default(); + + let listener = TcpListener::bind(&site_addr).await?; + + tracing::info!("Listening on http://{site_addr}/"); + + let router = Router::new() + .route( + "/", + get_service(ServeFile::new(dist_dir.join("index.html"))), + ) + .route("/assets/{*any}", get(static_files)) + .fallback(html_files) + .layer(CompressionLayer::new().gzip(true)) + .layer(TimeoutLayer::with_status_code( + StatusCode::REQUEST_TIMEOUT, + Duration::from_secs(30), + )) + .layer(TraceLayer::new_for_http()) + .with_state(AppState::new(dist_dir)); + + axum::serve(listener, router).await?; + Ok(()) +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..dfffbe0 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,18 @@ +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Clone)] +pub struct AppState { + dist_dir: Arc<PathBuf>, +} + +impl AppState { + pub fn new(dist_dir: PathBuf) -> Self { + let dist_dir = Arc::new(dist_dir); + + Self { dist_dir } + } + pub fn get_dist_dir(&self) -> Arc<PathBuf> { + Arc::clone(&self.dist_dir) + } +} |
