diff --git a/Cargo.lock b/Cargo.lock index 8b4c433..eaeacbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,31 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "askama" version = "0.12.1" @@ -107,7 +132,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper 1.0.0", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", @@ -149,6 +174,37 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "blake3" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "bustdir" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03176591ec0985c52fbf5b0676a878a69de1043f1d36eae033fc9c0775376f9e" +dependencies = [ + "ahash", + "askama", + "blake3", + "rand", +] + [[package]] name = "bytes" version = "1.6.0" @@ -157,9 +213,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" [[package]] name = "cfg-if" @@ -167,6 +223,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "equivalent" version = "1.0.1" @@ -218,6 +280,17 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -231,9 +304,11 @@ dependencies = [ "askama", "askama_axum", "axum", + "bustdir", "thiserror", "tokio", "tower", + "tower-http", "vss", ] @@ -302,6 +377,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" + [[package]] name = "httparse" version = "1.8.0" @@ -505,6 +586,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.79" @@ -516,13 +603,43 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -531,9 +648,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "serde" @@ -608,9 +725,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "thiserror" @@ -691,6 +808,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -890,3 +1032,23 @@ name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 3bfc67c..f158d6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ axum = { version = "0.7", features = ["tokio", "http1", "http2"], default-featur askama = { version = "0.12", features = ["with-axum"], default-features = false } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } askama_axum = { version = "0.4", default-features = false } +bustdir = { version = "0.1", features = ["askama"] } +tower-http = { version = "0.5", features = ["fs"] } thiserror = "1" tower = "0.4" vss = "0.1" diff --git a/src/main.css b/assets/main.css similarity index 100% rename from src/main.css rename to assets/main.css diff --git a/src/main.js b/assets/main.js similarity index 100% rename from src/main.js rename to assets/main.js diff --git a/src/main.rs b/src/main.rs index d4f16cd..56d2498 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,18 +11,25 @@ use std::{ use askama::Template; use axum::{ extract::{ConnectInfo, FromRequestParts, Request, State}, + handler::Handler, http::{ - header::{ACCESS_CONTROL_ALLOW_ORIGIN, CACHE_CONTROL, CONTENT_TYPE}, + header::{ACCESS_CONTROL_ALLOW_ORIGIN, CACHE_CONTROL}, request::Parts, HeaderName, HeaderValue, StatusCode, }, middleware::Next, - response::{Html, IntoResponse, Response}, + response::{IntoResponse, Response}, routing::{any, get}, Router, }; +use bustdir::BustDir; use tokio::net::TcpListener; use tower::ServiceBuilder; +use tower_http::services::ServeDir; + +mod filters { + pub use bustdir::askama::bust_dir; +} #[tokio::main] async fn main() { @@ -30,10 +37,13 @@ async fn main() { let v6_addr = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, port, 0, 0)); let state = AppState::new(); - let statics = Router::new() - .route("/main.css", get(css)) - .route("/main.js", get(js)) - .layer(axum::middleware::from_fn(noindex)); + + let serve_dir = ServeDir::new("assets").fallback(not_found.with_state(state.clone())); + let assets = ServiceBuilder::new() + .layer(axum::middleware::from_fn(noindex)) + .layer(axum::middleware::from_fn(infinicache)) + .service(serve_dir); + let app = Router::new() .route("/", get(home)) .route( @@ -45,8 +55,7 @@ async fn main() { ), ) .layer(axum::middleware::from_fn(nocache)) - .merge(statics) - .fallback(not_found) + .fallback_service(assets) .with_state(state.clone()); println!("Listening on http://localhost:{port} and http://{v6_addr} for ip requests"); @@ -65,10 +74,17 @@ async fn svc(tcp: TcpListener, app: Router) { #[template(path = "index.hbs", escape = "html", ext = "html")] pub struct IndexPage { root_dns_name: Arc, + cb: Arc, ip: IpAddr, https: bool, } +#[derive(Template)] +#[template(path = "404.hbs", escape = "html", ext = "html")] +pub struct NotFoundPage { + cb: Arc, +} + #[allow(clippy::unused_async)] async fn home( IpAddress(ip): IpAddress, @@ -79,6 +95,7 @@ async fn home( if accept.contains("text/html") { let page = IndexPage { root_dns_name: state.root_dns_name, + cb: state.cb, ip, https, }; @@ -94,35 +111,17 @@ async fn raw(IpAddress(ip): IpAddress) -> Result { } #[allow(clippy::unused_async)] -async fn css() -> ([(HeaderName, HeaderValue); 1], &'static str) { - static CSS_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("text/css;charset=UTF-8"); - ( - [(CONTENT_TYPE, CSS_CONTENT_TYPE.clone())], - include_str!("main.css"), - ) -} - -#[allow(clippy::unused_async)] -async fn js() -> ([(HeaderName, HeaderValue); 1], &'static str) { - static JS_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("text/javascript;charset=UTF-8"); - ( - [(CONTENT_TYPE, JS_CONTENT_TYPE.clone())], - include_str!("main.js"), - ) -} - -#[allow(clippy::unused_async)] -async fn not_found() -> Html<&'static str> { - Html(include_str!("404.html")) +async fn not_found(State(state): State) -> NotFoundPage { + NotFoundPage { cb: state.cb } } async fn nocache(request: Request, next: Next) -> Response { - static CACHE_CONTROL_VALUE: HeaderValue = HeaderValue::from_static("no-store"); + static CACHE_CONTROL_PRIVATE: HeaderValue = HeaderValue::from_static("no-store, private"); let mut response = next.run(request).await; response .headers_mut() - .insert(CACHE_CONTROL, CACHE_CONTROL_VALUE.clone()); + .insert(CACHE_CONTROL, CACHE_CONTROL_PRIVATE.clone()); response } @@ -146,10 +145,22 @@ async fn nocors(req: Request, next: Next) -> Response { resp } +async fn infinicache(request: Request, next: Next) -> Response { + static CACHE_CONTROL_1_YEAR: HeaderValue = + HeaderValue::from_static("immutable, public, max-age=3153600"); + + let mut response = next.run(request).await; + response + .headers_mut() + .insert(CACHE_CONTROL, CACHE_CONTROL_1_YEAR.clone()); + response +} + #[derive(Clone)] pub struct AppState { header: Option>, root_dns_name: Arc, + cb: Arc, } impl AppState { @@ -162,9 +173,12 @@ impl AppState { let root_dns_name: Arc = std::env::var("ROOT_DNS_NAME") .expect("No ROOT_DNS_NAME in env") .into(); + let bust = BustDir::new("assets").expect("Failed to create cache-bustin hashes"); + let cb = Arc::new(bust); Self { header: client_ip.map(|v| Arc::new(HeaderName::try_from(v).unwrap())), root_dns_name, + cb, } } } diff --git a/src/404.html b/templates/404.hbs similarity index 81% rename from src/404.html rename to templates/404.hbs index 3a5dc81..ccd3fcf 100644 --- a/src/404.html +++ b/templates/404.hbs @@ -3,7 +3,7 @@ - + 404 not found diff --git a/templates/index.hbs b/templates/index.hbs index 3f7bd4a..e3aaa5a 100644 --- a/templates/index.hbs +++ b/templates/index.hbs @@ -11,7 +11,7 @@ {%- match ip -%} @@ -30,7 +30,10 @@ crossorigin="anonymous" /> {%- endmatch -%} - + - +