use std::sync::Arc;
use tokio::sync::{OnceCell, RwLock};
mod config;
mod gc;
mod limiter;
mod types;
pub use config::*;
use limiter::RateLimiter;
pub use types::*;
static GLOBAL_LIMITER: OnceCell<Arc<RwLock<RateLimiter>>> = OnceCell::const_new();
#[macro_export]
macro_rules! init_rate_limiter {
(
default: $default_rule:expr
$(, max_memory: $max_memory:expr)?
$(, routes: [ $(($route:expr, $rule:expr)),* $(,)? ])?
) => {
{
let mut config = $crate::LimiterConfig::new($default_rule);
$(
if let Some(mem) = $max_memory {
config = config.with_max_memory(mem);
}
)?
$(
$(
config = config.add_route_rule($route, $rule);
)*
)?
$crate::initialize_limiter(config)
}
};
}
#[macro_export]
macro_rules! limit {
($who:expr, $route:expr) => {
$crate::check_limit($who, $route, None)
};
($who:expr, $route:expr, $method:expr) => {
$crate::check_limit($who, $route, Some($method))
};
}
#[macro_export]
macro_rules! limit_override {
($who:expr, $route:expr) => {
$crate::check_limit_override($who, $route, None)
};
($who:expr, $route:expr, $method:expr) => {
$crate::check_limit_override($who, $route, Some($method))
};
}
pub async fn initialize_limiter(config: LimiterConfig) {
let limiter = RateLimiter::new(config).await;
if GLOBAL_LIMITER.set(Arc::new(RwLock::new(limiter))).is_err() {
panic!("Rate limiter has already been initialized.");
}
}
pub async fn check_limit(who: &str, route: &str, method: Option<HttpMethod>) -> bool {
if let Some(limiter) = GLOBAL_LIMITER.get() {
let mut limiter = limiter.write().await;
limiter.check_limit(who, route, method, false).await
} else {
panic!("Rate limiter not initialized! Call init_rate_limiter! first.");
}
}
pub async fn check_limit_override(who: &str, route: &str, method: Option<HttpMethod>) -> bool {
if let Some(limiter) = GLOBAL_LIMITER.get() {
let mut limiter = limiter.write().await;
limiter.check_limit(who, route, method, true).await
} else {
panic!("Rate limiter not initialized! Call init_rate_limiter! first.");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Duration;
use std::time::Duration as StdDuration;
#[tokio::test]
async fn test_basic_rate_limiting() {
let config = LimiterConfig::new(RuleConfig::new(Duration::seconds(1), 1))
.add_route_rule(
"/prefix/",
RuleConfig::new(Duration::seconds(1), 2).match_prefix(true),
)
.add_route_rule(
"/method",
RuleConfig::new(Duration::seconds(1), 2).for_methods(vec![HttpMethod::POST]),
);
let limiter = RateLimiter::new(config).await;
let _ = GLOBAL_LIMITER.set(Arc::new(RwLock::new(limiter)));
let who = "test_ip";
let route = "/test";
assert!(check_limit(who, route, None).await);
assert!(!check_limit(who, route, None).await);
tokio::time::sleep(StdDuration::from_secs(1)).await;
assert!(check_limit(who, route, None).await);
let who = "test_ip";
let route = "/prefix/{test}";
assert!(check_limit(who, route, None).await);
assert!(check_limit(who, route, None).await);
assert!(!check_limit(who, route, None).await);
tokio::time::sleep(StdDuration::from_secs(1)).await;
assert!(check_limit(who, route, None).await);
let who = "test_ip";
let route = "/method";
assert!(check_limit(who, route, Some(HttpMethod::POST)).await);
assert!(check_limit(who, route, Some(HttpMethod::POST)).await);
assert!(!check_limit(who, route, Some(HttpMethod::POST)).await);
tokio::time::sleep(StdDuration::from_secs(1)).await;
assert!(check_limit(who, route, Some(HttpMethod::POST)).await);
assert!(check_limit(who, route, Some(HttpMethod::GET)).await);
assert!(!check_limit(who, route, Some(HttpMethod::GET)).await);
}
}