feat: cache mime types for valid assets, use octet-stream content type

This commit is contained in:
2025-09-13 12:37:36 -05:00
parent a917315967
commit 404a52e64c
4 changed files with 35 additions and 23 deletions

2
Cargo.lock generated
View File

@@ -218,7 +218,7 @@ dependencies = [
[[package]] [[package]]
name = "banner" name = "banner"
version = "0.2.2" version = "0.2.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "banner" name = "banner"
version = "0.2.2" version = "0.2.3"
edition = "2024" edition = "2024"
default-run = "banner" default-run = "banner"

View File

@@ -3,13 +3,9 @@
//! This module handles serving static assets that are embedded into the binary //! This module handles serving static assets that are embedded into the binary
//! at compile time using rust-embed. //! at compile time using rust-embed.
use axum::{ use dashmap::DashMap;
extract::Path, use once_cell::sync::Lazy;
http::{StatusCode, header},
response::{Html, IntoResponse, Response},
};
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use tracing::debug;
/// Embedded web assets from the dist directory /// Embedded web assets from the dist directory
#[derive(RustEmbed)] #[derive(RustEmbed)]
@@ -18,18 +14,24 @@ use tracing::debug;
#[exclude = "*.map"] #[exclude = "*.map"]
pub struct WebAssets; pub struct WebAssets;
const ASSET_EXTENSIONS: &[&str] = &[ /// Global cache for MIME types to avoid repeated mime_guess lookups
"js", "css", "png", "jpg", "jpeg", "gif", "svg", "ico", "woff", "woff2", "ttf", "eot", static MIME_CACHE: Lazy<DashMap<String, Option<String>>> = Lazy::new(DashMap::new);
];
/// Check if a path should be served as a static asset /// Get cached MIME type for a file path, caching on-demand
pub fn is_asset_path(path: &str) -> bool { /// Returns None if the MIME type is text/plain or if no MIME type could be determined
if !path.starts_with("/assets/") { pub fn get_mime_type_cached(path: &str) -> Option<String> {
return path.eq("index.html"); // Check cache first
if let Some(cached) = MIME_CACHE.get(path) {
return cached.value().as_ref().cloned();
} }
match path.split_once('.') { // Perform MIME guess and cache the result
Some((_, extension)) => ASSET_EXTENSIONS.contains(&extension), let result = mime_guess::from_path(path)
None => false, .first()
} .map(|mime| mime.to_string());
// Cache the result
MIME_CACHE.insert(path.to_string(), result.clone());
result
} }

View File

@@ -14,9 +14,9 @@ use tower_http::{
cors::{Any, CorsLayer}, cors::{Any, CorsLayer},
trace::TraceLayer, 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; use crate::banner::BannerApi;
@@ -82,9 +82,19 @@ async fn handle_spa_fallback(uri: Uri) -> Response {
let path = uri.path().trim_start_matches('/'); let path = uri.path().trim_start_matches('/');
if let Some(content) = WebAssets::get(path) { 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(); 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 { } else {
// Any assets that are not found should be treated as a 404, not falling back to the SPA index.html // 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/") { if path.starts_with("assets/") {