#[cfg(test)]
mod tests;
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex, OnceLock};
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::time::{Duration, Instant};
pub struct RateLimiter {
state: Mutex<HashMap<String, VecDeque<Instant>>>,
max_requests: AtomicU32,
window_secs: AtomicU64,
}
impl RateLimiter {
pub fn new(max_requests: u32, window_secs: u64) -> Self {
RateLimiter {
state: Mutex::new(HashMap::new()),
max_requests: AtomicU32::new(max_requests),
window_secs: AtomicU64::new(window_secs),
}
}
pub fn set_limits(&self, max_requests: u32, window_secs: u64) {
self.max_requests.store(max_requests, Ordering::Relaxed);
self.window_secs.store(window_secs, Ordering::Relaxed);
}
fn window(&self) -> Duration {
Duration::from_secs(self.window_secs.load(Ordering::Relaxed))
}
fn max(&self) -> u32 {
self.max_requests.load(Ordering::Relaxed)
}
pub fn check(&self, key: &str) -> bool {
let now = Instant::now();
let window = self.window();
let max = self.max();
let mut guard = self.state.lock().unwrap();
let timestamps = guard.entry(key.to_string()).or_default();
while timestamps.front().map(|t| now.duration_since(*t) > window).unwrap_or(false) {
timestamps.pop_front();
}
if (timestamps.len() as u32) < max {
timestamps.push_back(now);
true
} else {
false
}
}
pub fn remaining(&self, key: &str) -> u32 {
let now = Instant::now();
let window = self.window();
let max = self.max();
let mut guard = self.state.lock().unwrap();
let timestamps = guard.entry(key.to_string()).or_default();
while timestamps.front().map(|t| now.duration_since(*t) > window).unwrap_or(false) {
timestamps.pop_front();
}
max.saturating_sub(timestamps.len() as u32)
}
pub fn reset(&self, key: &str) {
self.state.lock().unwrap().remove(key);
}
}
use crate::redis_protocol::{RespConn, RespReply};
pub struct RedisRateLimiter {
conn: Arc<RespConn>,
max_requests: AtomicU32,
window_secs: AtomicU64,
}
impl Clone for RedisRateLimiter {
fn clone(&self) -> Self {
RedisRateLimiter {
conn: Arc::clone(&self.conn),
max_requests: AtomicU32::new(self.max()),
window_secs: AtomicU64::new(self.window().as_secs()),
}
}
}
impl RedisRateLimiter {
pub fn new(addr: impl Into<String>, password: Option<String>, max_requests: u32, window_secs: u64) -> Self {
RedisRateLimiter {
conn: Arc::new(RespConn::new(addr, password)),
max_requests: AtomicU32::new(max_requests),
window_secs: AtomicU64::new(window_secs),
}
}
pub fn from_env() -> Self {
let host = std::env::var("RWS_REDIS_HOST").unwrap_or_else(|_| "127.0.0.1".into());
let port = std::env::var("RWS_REDIS_PORT").unwrap_or_else(|_| "6379".into());
let addr = format!("{}:{}", host, port);
let password = std::env::var("RWS_REDIS_PASSWORD").ok();
let max: u32 = std::env::var("RWS_CONFIG_RATE_LIMIT_MAX_REQUESTS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1000);
let window: u64 = std::env::var("RWS_CONFIG_RATE_LIMIT_WINDOW_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(60);
Self::new(addr, password, max, window)
}
pub fn set_limits(&self, max_requests: u32, window_secs: u64) {
self.max_requests.store(max_requests, Ordering::Relaxed);
self.window_secs.store(window_secs, Ordering::Relaxed);
}
fn window(&self) -> Duration {
Duration::from_secs(self.window_secs.load(Ordering::Relaxed))
}
fn max(&self) -> u32 {
self.max_requests.load(Ordering::Relaxed)
}
fn redis_key(key: &str) -> Vec<u8> {
format!("rws:ratelimit:{}", key).into_bytes()
}
pub fn check(&self, key: &str) -> std::io::Result<bool> {
let redis_key = Self::redis_key(key);
let window = self.window().as_secs().to_string();
self.conn.cmd(&[b"SET", &redis_key, b"0", b"EX", window.as_bytes(), b"NX"])?;
let count = match self.conn.cmd(&[b"INCR", &redis_key])? {
RespReply::Int(n) => n,
_ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unexpected INCR reply")),
};
Ok(count as u32 <= self.max())
}
pub fn remaining(&self, key: &str) -> std::io::Result<u32> {
match self.conn.cmd(&[b"GET", &Self::redis_key(key)])? {
RespReply::Bulk(Some(bytes)) => {
let count: u32 = String::from_utf8_lossy(&bytes).parse().unwrap_or(0);
Ok(self.max().saturating_sub(count))
}
_ => Ok(self.max()),
}
}
pub fn reset(&self, key: &str) -> std::io::Result<()> {
self.conn.cmd(&[b"DEL", &Self::redis_key(key)])?;
Ok(())
}
}
static GLOBAL_LIMITER: OnceLock<RateLimiter> = OnceLock::new();
pub fn global() -> &'static RateLimiter {
GLOBAL_LIMITER.get_or_init(|| {
let max: u32 = std::env::var("RWS_CONFIG_RATE_LIMIT_MAX_REQUESTS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(1000);
let window: u64 = std::env::var("RWS_CONFIG_RATE_LIMIT_WINDOW_SECS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(60);
RateLimiter::new(max, window)
})
}