summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml14
-rw-r--r--public/index.html4
-rw-r--r--public/music.html4
-rw-r--r--src/env.rs20
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs91
-rw-r--r--src/state.rs18
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)
+ }
+}