From 404a52e64cb6744510ebf70b995970fb3e2573aa Mon Sep 17 00:00:00 2001 From: Xevion Date: Sat, 13 Sep 2025 12:37:36 -0500 Subject: [PATCH] feat: cache mime types for valid assets, use octet-stream content type --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/web/assets.rs | 36 +++++++++++++++++++----------------- src/web/routes.rs | 18 ++++++++++++++---- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71e0024..ad84516 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,7 +218,7 @@ dependencies = [ [[package]] name = "banner" -version = "0.2.2" +version = "0.2.3" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index aa7b0b1..5caa628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "banner" -version = "0.2.2" +version = "0.2.3" edition = "2024" default-run = "banner" diff --git a/src/web/assets.rs b/src/web/assets.rs index ca5685a..bf31555 100644 --- a/src/web/assets.rs +++ b/src/web/assets.rs @@ -3,13 +3,9 @@ //! This module handles serving static assets that are embedded into the binary //! at compile time using rust-embed. -use axum::{ - extract::Path, - http::{StatusCode, header}, - response::{Html, IntoResponse, Response}, -}; +use dashmap::DashMap; +use once_cell::sync::Lazy; use rust_embed::RustEmbed; -use tracing::debug; /// Embedded web assets from the dist directory #[derive(RustEmbed)] @@ -18,18 +14,24 @@ use tracing::debug; #[exclude = "*.map"] pub struct WebAssets; -const ASSET_EXTENSIONS: &[&str] = &[ - "js", "css", "png", "jpg", "jpeg", "gif", "svg", "ico", "woff", "woff2", "ttf", "eot", -]; +/// Global cache for MIME types to avoid repeated mime_guess lookups +static MIME_CACHE: Lazy>> = Lazy::new(DashMap::new); -/// Check if a path should be served as a static asset -pub fn is_asset_path(path: &str) -> bool { - if !path.starts_with("/assets/") { - return path.eq("index.html"); +/// Get cached MIME type for a file path, caching on-demand +/// Returns None if the MIME type is text/plain or if no MIME type could be determined +pub fn get_mime_type_cached(path: &str) -> Option { + // Check cache first + if let Some(cached) = MIME_CACHE.get(path) { + return cached.value().as_ref().cloned(); } - match path.split_once('.') { - Some((_, extension)) => ASSET_EXTENSIONS.contains(&extension), - None => false, - } + // Perform MIME guess and cache the result + let result = mime_guess::from_path(path) + .first() + .map(|mime| mime.to_string()); + + // Cache the result + MIME_CACHE.insert(path.to_string(), result.clone()); + + result } diff --git a/src/web/routes.rs b/src/web/routes.rs index 9559ed8..32a09a3 100644 --- a/src/web/routes.rs +++ b/src/web/routes.rs @@ -14,9 +14,9 @@ use tower_http::{ cors::{Any, CorsLayer}, trace::TraceLayer, }; -use tracing::{debug, info}; +use tracing::info; -use crate::web::assets::WebAssets; +use crate::web::assets::{WebAssets, get_mime_type_cached}; use crate::banner::BannerApi; @@ -82,9 +82,19 @@ async fn handle_spa_fallback(uri: Uri) -> Response { let path = uri.path().trim_start_matches('/'); if let Some(content) = WebAssets::get(path) { - let mime_type = mime_guess::from_path(path).first_or_text_plain(); let data = content.data.to_vec(); - return ([(header::CONTENT_TYPE, mime_type.as_ref())], data).into_response(); + + // Use cached MIME type, only set Content-Type if we have a valid MIME type + let mime_type = get_mime_type_cached(path); + return ( + [( + header::CONTENT_TYPE, + // For unknown types, set to application/octet-stream + mime_type.unwrap_or("application/octet-stream".to_string()), + )], + data, + ) + .into_response(); } else { // Any assets that are not found should be treated as a 404, not falling back to the SPA index.html if path.starts_with("assets/") {