mentedb_server/
rate_limit.rs1use std::collections::HashMap;
8use std::net::IpAddr;
9use std::sync::Arc;
10use std::task::{Context, Poll};
11use std::time::Instant;
12
13use axum::body::Body;
14use axum::http::{Request, StatusCode};
15use axum::response::IntoResponse;
16use serde_json::json;
17use tokio::sync::Mutex;
18use tower::{Layer, Service};
19
20pub const DEFAULT_BURST: u32 = 100;
22pub const DEFAULT_PER_SEC: u32 = 10;
24const MAX_TRACKED_KEYS: usize = 100_000;
26
27#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29enum Key {
30 Caller(String),
31 Ip(IpAddr),
32}
33
34struct Bucket {
36 tokens: f64,
37 last_refill: Instant,
38}
39
40#[derive(Clone)]
42pub struct RateLimiter {
43 buckets: Arc<Mutex<HashMap<Key, Bucket>>>,
44 max_tokens: u32,
45 refill_rate: u32,
46}
47
48impl RateLimiter {
49 pub fn new(max_tokens: u32, refill_rate: u32) -> Self {
52 Self {
53 buckets: Arc::new(Mutex::new(HashMap::new())),
54 max_tokens,
55 refill_rate,
56 }
57 }
58
59 pub fn from_env() -> Self {
62 let read = |name: &str, default: u32| {
63 std::env::var(name)
64 .ok()
65 .and_then(|v| v.trim().parse::<u32>().ok())
66 .unwrap_or(default)
67 };
68 Self::new(
69 read("MENTEDB_RATE_LIMIT_BURST", DEFAULT_BURST),
70 read("MENTEDB_RATE_LIMIT_PER_SEC", DEFAULT_PER_SEC),
71 )
72 }
73
74 fn enabled(&self) -> bool {
75 self.max_tokens > 0 && self.refill_rate > 0
76 }
77
78 async fn allow(&self, key: Key) -> bool {
79 let now = Instant::now();
80 let max = f64::from(self.max_tokens);
81 let rate = f64::from(self.refill_rate);
82 let mut buckets = self.buckets.lock().await;
83 if buckets.len() >= MAX_TRACKED_KEYS {
84 buckets.retain(|_, b| {
86 b.tokens + now.duration_since(b.last_refill).as_secs_f64() * rate < max
87 });
88 }
89 let bucket = buckets.entry(key).or_insert(Bucket {
90 tokens: max,
91 last_refill: now,
92 });
93 let elapsed = now.duration_since(bucket.last_refill).as_secs_f64();
94 bucket.tokens = (bucket.tokens + elapsed * rate).min(max);
95 bucket.last_refill = now;
96 if bucket.tokens >= 1.0 {
97 bucket.tokens -= 1.0;
98 true
99 } else {
100 false
101 }
102 }
103}
104
105impl Default for RateLimiter {
106 fn default() -> Self {
107 Self::new(DEFAULT_BURST, DEFAULT_PER_SEC)
108 }
109}
110
111impl<S> Layer<S> for RateLimiter {
114 type Service = RateLimitService<S>;
115
116 fn layer(&self, inner: S) -> Self::Service {
117 RateLimitService {
118 inner,
119 limiter: self.clone(),
120 }
121 }
122}
123
124#[derive(Clone)]
127pub struct RateLimitService<S> {
128 inner: S,
129 limiter: RateLimiter,
130}
131
132impl<S> Service<Request<Body>> for RateLimitService<S>
133where
134 S: Service<Request<Body>, Response = axum::response::Response> + Clone + Send + 'static,
135 S::Future: Send + 'static,
136{
137 type Response = axum::response::Response;
138 type Error = S::Error;
139 type Future = std::pin::Pin<
140 Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + Send>,
141 >;
142
143 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
144 self.inner.poll_ready(cx)
145 }
146
147 fn call(&mut self, req: Request<Body>) -> Self::Future {
148 let limiter = self.limiter.clone();
149 let mut inner = self.inner.clone();
150
151 let key = req
152 .extensions()
153 .get::<crate::auth::AuthenticatedAgent>()
154 .and_then(|a| a.rate_key())
155 .map(Key::Caller)
156 .unwrap_or_else(|| {
157 Key::Ip(
158 req.extensions()
159 .get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
160 .map(|ci| ci.0.ip())
161 .unwrap_or(IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)),
162 )
163 });
164
165 Box::pin(async move {
166 if !limiter.enabled() || limiter.allow(key).await {
167 inner.call(req).await
168 } else {
169 let body = json!({ "error": "too many requests" });
170 Ok((StatusCode::TOO_MANY_REQUESTS, axum::Json(body)).into_response())
171 }
172 })
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[tokio::test]
181 async fn buckets_are_per_key_and_refill() {
182 let l = RateLimiter::new(2, 1000);
183 let a = Key::Caller("alice".into());
184 let b = Key::Caller("bob".into());
185 assert!(l.allow(a.clone()).await);
186 assert!(l.allow(a.clone()).await);
187 assert!(!l.allow(a.clone()).await, "alice's burst is spent");
188 assert!(l.allow(b.clone()).await, "bob has his own bucket");
189 tokio::time::sleep(std::time::Duration::from_millis(5)).await;
190 assert!(l.allow(a).await, "refilled");
191 }
192
193 #[test]
194 fn zero_turns_limiting_off() {
195 assert!(!RateLimiter::new(0, 10).enabled());
196 assert!(!RateLimiter::new(10, 0).enabled());
197 assert!(RateLimiter::new(1, 1).enabled());
198 }
199}