#[cfg(test)]
mod tests;
use std::sync::{OnceLock, RwLock};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use crate::entry_point::Config;
use crate::entry_point::config_file::override_environment_variables_from_config;
use crate::rate_limit;
pub static RELOAD_REQUESTED: AtomicBool = AtomicBool::new(false);
static RELOADS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
fn last_errors() -> &'static std::sync::Mutex<Vec<String>> {
static ERRORS: OnceLock<std::sync::Mutex<Vec<String>>> = OnceLock::new();
ERRORS.get_or_init(|| std::sync::Mutex::new(Vec::new()))
}
pub fn last_reload() -> (u64, Vec<String>) {
let errors = last_errors().lock().unwrap_or_else(|e| e.into_inner()).clone();
(RELOADS.load(Ordering::SeqCst), errors)
}
#[cfg(test)]
pub(crate) fn on_reload_test_error() {
let mut errors = last_errors().lock().unwrap_or_else(|e| e.into_inner());
errors.clear();
errors.push("test reload error".to_string());
drop(errors);
RELOADS.fetch_add(1, Ordering::SeqCst);
}
pub fn report_error(message: impl Into<String>) {
last_errors().lock().unwrap_or_else(|e| e.into_inner()).push(message.into());
}
#[derive(Debug, Clone)]
pub struct ConfigSnapshot {
pub cors_allow_all: bool,
pub cors_allow_origins: String,
pub cors_allow_credentials: String,
pub cors_allow_methods: String,
pub cors_allow_headers: String,
pub cors_expose_headers: String,
pub cors_max_age: String,
pub rate_limit_max_requests: u32,
pub rate_limit_window_secs: u64,
pub log_format: String,
pub request_allocation_size: i64,
pub max_body_size: u64,
}
impl ConfigSnapshot {
fn from_env() -> Self {
let read = |key: &str| std::env::var(key).unwrap_or_default();
Self {
cors_allow_all: read(Config::RWS_CONFIG_CORS_ALLOW_ALL)
.eq_ignore_ascii_case("true"),
cors_allow_origins: read(Config::RWS_CONFIG_CORS_ALLOW_ORIGINS),
cors_allow_credentials: read(Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS),
cors_allow_methods: read(Config::RWS_CONFIG_CORS_ALLOW_METHODS),
cors_allow_headers: read(Config::RWS_CONFIG_CORS_ALLOW_HEADERS),
cors_expose_headers: read(Config::RWS_CONFIG_CORS_EXPOSE_HEADERS),
cors_max_age: read(Config::RWS_CONFIG_CORS_MAX_AGE),
rate_limit_max_requests: std::env::var("RWS_CONFIG_RATE_LIMIT_MAX_REQUESTS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1000),
rate_limit_window_secs: std::env::var("RWS_CONFIG_RATE_LIMIT_WINDOW_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(60),
log_format: read(Config::RWS_CONFIG_LOG_FORMAT),
request_allocation_size: std::env::var(
Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES,
)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(*Config::RWS_DEFAULT_REQUEST_ALLOCATION_SIZE_IN_BYTES),
max_body_size: crate::entry_point::get_max_body_size(),
}
}
}
static SNAPSHOT: OnceLock<RwLock<ConfigSnapshot>> = OnceLock::new();
fn global() -> &'static RwLock<ConfigSnapshot> {
SNAPSHOT.get_or_init(|| RwLock::new(ConfigSnapshot::from_env()))
}
pub fn current() -> ConfigSnapshot {
global().read().unwrap().clone()
}
pub fn request_reload() {
#[cfg(all(unix, any(feature = "http1", feature = "http2")))]
unsafe {
libc::kill(libc::getpid(), libc::SIGHUP);
}
#[cfg(not(all(unix, any(feature = "http1", feature = "http2"))))]
reload();
}
pub fn reload() {
last_errors().lock().unwrap_or_else(|e| e.into_inner()).clear();
override_environment_variables_from_config(None);
crate::server_config::invalidate();
let snapshot = ConfigSnapshot::from_env();
rate_limit::global().set_limits(
snapshot.rate_limit_max_requests,
snapshot.rate_limit_window_secs,
);
*global().write().unwrap() = snapshot.clone();
#[cfg(feature = "tera")]
crate::template::reload_if_initialized();
for hook in hooks().lock().unwrap_or_else(|e| e.into_inner()).iter() {
hook();
}
RELOADS.fetch_add(1, Ordering::SeqCst);
println!(
"Config reloaded — cors_allow_all={} rate_limit={}/{} log_format={}",
snapshot.cors_allow_all,
snapshot.rate_limit_max_requests,
snapshot.rate_limit_window_secs,
snapshot.log_format,
);
}
type ReloadHook = Box<dyn Fn() + Send + Sync>;
fn hooks() -> &'static std::sync::Mutex<Vec<ReloadHook>> {
static HOOKS: OnceLock<std::sync::Mutex<Vec<ReloadHook>>> = OnceLock::new();
HOOKS.get_or_init(|| std::sync::Mutex::new(Vec::new()))
}
pub fn on_reload(hook: impl Fn() + Send + Sync + 'static) {
hooks().lock().unwrap_or_else(|e| e.into_inner()).push(Box::new(hook));
}
pub fn install_sighup_handler() {
#[cfg(all(unix, feature = "http1"))]
unsafe {
libc::signal(libc::SIGHUP, sighup_handler as *const () as libc::sighandler_t);
}
}
#[cfg(all(unix, feature = "http1"))]
extern "C" fn sighup_handler(_: libc::c_int) {
RELOAD_REQUESTED.store(true, Ordering::SeqCst);
}