performance improvements
This commit is contained in:
+32
-4
@@ -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()
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user