performance improvements

This commit is contained in:
2026-05-14 17:52:13 +02:00
parent 6bc51d6d14
commit 046f60dcb6
9 changed files with 299 additions and 134 deletions
+32 -4
View File
@@ -9,19 +9,23 @@ use axum::{
http::{HeaderValue, header},
routing::{delete, get, post},
};
use std::{collections::HashMap, env, fs, path::PathBuf, sync::Arc};
use tokio::sync::Mutex;
use std::{collections::HashMap, env, path::PathBuf, sync::Arc, time::Duration};
use tokio::sync::{Mutex, RwLock};
use tower_http::{
cors::{AllowOrigin, CorsLayer},
services::ServeDir,
};
use tracing::{error, info, warn};
use crate::handlers::contact::RATE_LIMIT_WINDOW_MS;
use crate::models::PostInfo;
pub struct AppState {
pub admin_token: String,
pub data_dir: PathBuf,
pub cookie_secure: bool,
pub post_lock: Mutex<()>,
pub posts_cache: RwLock<Vec<PostInfo>>,
pub contact_rate_limit: Mutex<HashMap<String, Vec<i64>>>,
}
@@ -50,10 +54,10 @@ async fn main() {
let posts_dir = data_dir.join("posts");
let uploads_dir = data_dir.join("uploads");
if let Err(e) = fs::create_dir_all(&posts_dir) {
if let Err(e) = tokio::fs::create_dir_all(&posts_dir).await {
error!("Failed to create posts directory: {}", e);
}
if let Err(e) = fs::create_dir_all(&uploads_dir) {
if let Err(e) = tokio::fs::create_dir_all(&uploads_dir).await {
error!("Failed to create uploads directory: {}", e);
}
@@ -62,9 +66,15 @@ async fn main() {
data_dir,
cookie_secure,
post_lock: Mutex::new(()),
posts_cache: RwLock::new(Vec::new()),
contact_rate_limit: Mutex::new(HashMap::new()),
});
handlers::posts::rebuild_posts_cache(&state).await;
info!("Posts cache primed with {} entries", state.posts_cache.read().await.len());
spawn_rate_limit_reaper(state.clone());
// CORS — locked down by default. Set FRONTEND_ORIGIN to the public URL of
// the frontend if you ever expose the backend directly to browsers.
// Normal deployments hit the backend through the Astro proxy, which is
@@ -126,3 +136,21 @@ async fn main() {
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}
/// Periodically prunes expired entries from the contact rate-limit map so it
/// can't grow unbounded across the lifetime of the process.
fn spawn_rate_limit_reaper(state: Arc<AppState>) {
tokio::spawn(async move {
let mut ticker = tokio::time::interval(Duration::from_secs(300));
ticker.tick().await;
loop {
ticker.tick().await;
let now_ms = chrono::Utc::now().timestamp_millis();
let mut map = state.contact_rate_limit.lock().await;
map.retain(|_, times| {
times.retain(|t| now_ms - *t < RATE_LIMIT_WINDOW_MS);
!times.is_empty()
});
}
});
}