commit a0d7b85a3154cf137f810b6f774e4324298b8fed
parent 866227f55e4edfc11bea767e83ef15c37d3df14e
Author: Silas Brack <silasbrack@gmail.com>
Date: Tue, 21 Apr 2026 22:01:54 +0200
fix: embed static files with rust-embed
Serves pre-compressed .gz files with proper content-encoding header.
Single binary, no working directory dependency.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
| M | Cargo.lock | | | 77 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
| M | Cargo.toml | | | 4 | +++- |
| M | src/routes.rs | | | 39 | +++++++++++++++++++++++++++++++++++++-- |
3 files changed, 104 insertions(+), 16 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -734,12 +734,6 @@ dependencies = [
]
[[package]]
-name = "http-range-header"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
-
-[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1363,6 +1357,40 @@ dependencies = [
]
[[package]]
+name = "rust-embed"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1375,6 +1403,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1519,6 +1556,8 @@ dependencies = [
"chrono",
"dotenvy",
"futures",
+ "mime_guess",
+ "rust-embed",
"serde",
"sqlx",
"thiserror",
@@ -1947,15 +1986,8 @@ dependencies = [
"bitflags",
"bytes",
"futures-core",
- "futures-util",
"http",
"http-body",
- "http-body-util",
- "http-range-header",
- "httpdate",
- "mime",
- "mime_guess",
- "percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
@@ -2114,6 +2146,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2181,6 +2223,15 @@ dependencies = [
]
[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,7 +9,9 @@ askama = { version = "0.12", features = ["with-axum"] }
askama_axum = "0.4"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono"] }
tokio = { version = "1", features = ["full"] }
-tower-http = { version = "0.6", features = ["fs", "trace", "compression-gzip"] }
+tower-http = { version = "0.6", features = ["trace", "compression-gzip"] }
+rust-embed = "8"
+mime_guess = "2"
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
dotenvy = "0.15"
diff --git a/src/routes.rs b/src/routes.rs
@@ -1,12 +1,47 @@
use axum::{
Router,
+ body::Body,
+ extract::Path,
+ http::{StatusCode, header},
+ response::{IntoResponse, Response},
routing::{get, post},
};
-use tower_http::services::ServeDir;
+use rust_embed::Embed;
use crate::handlers;
use crate::state::AppState;
+#[derive(Embed)]
+#[folder = "static/"]
+struct Assets;
+
+async fn static_handler(Path(path): Path<String>) -> Response {
+ // Try .gz version first if it exists
+ let gz_path = format!("{}.gz", path);
+ if let Some(file) = Assets::get(&gz_path) {
+ let mime = mime_guess::from_path(&path).first_or_octet_stream();
+ return Response::builder()
+ .status(StatusCode::OK)
+ .header(header::CONTENT_TYPE, mime.as_ref())
+ .header(header::CONTENT_ENCODING, "gzip")
+ .body(Body::from(file.data.into_owned()))
+ .unwrap();
+ }
+
+ // Fall back to uncompressed
+ match Assets::get(&path) {
+ Some(file) => {
+ let mime = mime_guess::from_path(&path).first_or_octet_stream();
+ Response::builder()
+ .status(StatusCode::OK)
+ .header(header::CONTENT_TYPE, mime.as_ref())
+ .body(Body::from(file.data.into_owned()))
+ .unwrap()
+ }
+ None => StatusCode::NOT_FOUND.into_response(),
+ }
+}
+
pub fn create_router(state: AppState) -> Router {
Router::new()
.route("/", get(handlers::show_home_page))
@@ -14,6 +49,6 @@ pub fn create_router(state: AppState) -> Router {
.route("/search", post(handlers::search))
.route("/settings", get(handlers::open_settings))
.route("/settings/tab", get(handlers::open_settings_tab))
- .nest_service("/static", ServeDir::new("static").precompressed_gzip())
+ .route("/static/{*path}", get(static_handler))
.with_state(state)
}