1use std::collections::HashMap;
4use std::net::IpAddr;
5use std::sync::RwLock;
6use std::time::{Duration, Instant};
7
8use axum::{
9 extract::{ConnectInfo, Request, State},
10 http::StatusCode,
11 middleware::Next,
12 response::{IntoResponse, Response},
13};
14
15#[derive(Debug, Clone)]
17pub struct RateLimitConfig {
18 pub max_requests: u32,
20 pub window: Duration,
22 pub enabled: bool,
24 pub max_tracked_ips: usize,
26}
27
28impl Default for RateLimitConfig {
29 fn default() -> Self {
30 Self {
31 max_requests: 100,
32 window: Duration::from_secs(60),
33 enabled: true,
34 max_tracked_ips: 10000,
35 }
36 }
37}
38
39impl RateLimitConfig {
40 pub fn disabled() -> Self {
42 Self {
43 enabled: false,
44 ..Default::default()
45 }
46 }
47
48 pub fn strict() -> Self {
50 Self {
51 max_requests: 10,
52 window: Duration::from_secs(60),
53 ..Default::default()
54 }
55 }
56
57 pub fn relaxed() -> Self {
59 Self {
60 max_requests: 1000,
61 window: Duration::from_secs(60),
62 ..Default::default()
63 }
64 }
65
66 pub fn custom(max_requests: u32, window_secs: u64) -> Self {
68 Self {
69 max_requests,
70 window: Duration::from_secs(window_secs),
71 ..Default::default()
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78struct RequestRecord {
79 timestamps: Vec<Instant>,
81}
82
83impl RequestRecord {
84 fn new() -> Self {
85 Self {
86 timestamps: Vec::new(),
87 }
88 }
89
90 fn clean_and_count(&mut self, window: Duration) -> u32 {
92 let now = Instant::now();
93 let cutoff = now - window;
94
95 self.timestamps.retain(|&t| t > cutoff);
97
98 self.timestamps.len() as u32
99 }
100
101 fn record(&mut self) {
103 self.timestamps.push(Instant::now());
104 }
105}
106
107#[derive(Debug)]
109pub struct RateLimiter {
110 records: RwLock<HashMap<IpAddr, RequestRecord>>,
111 config: RateLimitConfig,
112 last_cleanup: RwLock<Instant>,
113}
114
115impl RateLimiter {
116 pub fn new(config: RateLimitConfig) -> Self {
118 Self {
119 records: RwLock::new(HashMap::new()),
120 config,
121 last_cleanup: RwLock::new(Instant::now()),
122 }
123 }
124
125 pub fn disabled() -> Self {
127 Self::new(RateLimitConfig::disabled())
128 }
129
130 pub fn is_enabled(&self) -> bool {
132 self.config.enabled
133 }
134
135 pub fn check(&self, ip: IpAddr) -> Result<u32, Duration> {
139 if !self.config.enabled {
140 return Ok(self.config.max_requests);
141 }
142
143 self.maybe_cleanup();
145
146 let mut records = match self.records.write() {
147 Ok(r) => r,
148 Err(_) => return Ok(self.config.max_requests), };
150
151 let record = records.entry(ip).or_insert_with(RequestRecord::new);
152 let current_count = record.clean_and_count(self.config.window);
153
154 if current_count >= self.config.max_requests {
155 let oldest = record.timestamps.first().copied();
157 let retry_after = oldest
158 .map(|t| self.config.window.saturating_sub(t.elapsed()))
159 .unwrap_or(self.config.window);
160 return Err(retry_after);
161 }
162
163 record.record();
165 let remaining = self.config.max_requests - current_count - 1;
166
167 Ok(remaining)
168 }
169
170 fn maybe_cleanup(&self) {
172 let should_cleanup = self
173 .last_cleanup
174 .read()
175 .map(|t| t.elapsed() > self.config.window * 2)
176 .unwrap_or(false);
177
178 if !should_cleanup {
179 return;
180 }
181
182 if let Ok(mut last) = self.last_cleanup.write() {
184 if last.elapsed() <= self.config.window * 2 {
186 return;
187 }
188
189 *last = Instant::now();
190
191 if let Ok(mut records) = self.records.write() {
192 let cutoff = Instant::now() - self.config.window * 2;
193
194 records.retain(|_, record| {
196 record
197 .timestamps
198 .last()
199 .map(|&t| t > cutoff)
200 .unwrap_or(false)
201 });
202
203 if records.len() > self.config.max_tracked_ips {
205 let mut entries: Vec<_> = records
206 .iter()
207 .map(|(ip, r)| (*ip, r.timestamps.last().copied()))
208 .collect();
209
210 entries.sort_by_key(|(_, t)| *t);
211
212 let to_remove = records.len() - self.config.max_tracked_ips;
213 for (ip, _) in entries.into_iter().take(to_remove) {
214 records.remove(&ip);
215 }
216 }
217 }
218 }
219 }
220
221 pub fn stats(&self) -> RateLimitStats {
223 let tracked_ips = self.records.read().map(|r| r.len()).unwrap_or(0);
224 RateLimitStats {
225 tracked_ips,
226 max_requests: self.config.max_requests,
227 window_secs: self.config.window.as_secs(),
228 enabled: self.config.enabled,
229 }
230 }
231}
232
233impl Default for RateLimiter {
234 fn default() -> Self {
235 Self::new(RateLimitConfig::default())
236 }
237}
238
239#[derive(Debug, Clone)]
241pub struct RateLimitStats {
242 pub tracked_ips: usize,
243 pub max_requests: u32,
244 pub window_secs: u64,
245 pub enabled: bool,
246}
247
248pub async fn rate_limit_middleware(
250 State(limiter): State<std::sync::Arc<RateLimiter>>,
251 ConnectInfo(addr): ConnectInfo<std::net::SocketAddr>,
252 request: Request,
253 next: Next,
254) -> Response {
255 if request.uri().path() == "/health" {
257 return next.run(request).await;
258 }
259
260 match limiter.check(addr.ip()) {
261 Ok(remaining) => {
262 let mut response = next.run(request).await;
263
264 let headers = response.headers_mut();
266 headers.insert(
267 "X-RateLimit-Limit",
268 limiter.config.max_requests.to_string().parse().unwrap(),
269 );
270 headers.insert(
271 "X-RateLimit-Remaining",
272 remaining.to_string().parse().unwrap(),
273 );
274
275 response
276 }
277 Err(retry_after) => {
278 let mut response = (
279 StatusCode::TOO_MANY_REQUESTS,
280 "Rate limit exceeded. Please try again later.",
281 )
282 .into_response();
283
284 response.headers_mut().insert(
285 "Retry-After",
286 retry_after.as_secs().to_string().parse().unwrap(),
287 );
288 response.headers_mut().insert(
289 "X-RateLimit-Limit",
290 limiter.config.max_requests.to_string().parse().unwrap(),
291 );
292 response
293 .headers_mut()
294 .insert("X-RateLimit-Remaining", "0".parse().unwrap());
295
296 response
297 }
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use std::net::{Ipv4Addr, Ipv6Addr};
305
306 #[test]
307 fn test_rate_limit_config_default() {
308 let config = RateLimitConfig::default();
309 assert_eq!(config.max_requests, 100);
310 assert_eq!(config.window, Duration::from_secs(60));
311 assert!(config.enabled);
312 }
313
314 #[test]
315 fn test_rate_limit_config_disabled() {
316 let config = RateLimitConfig::disabled();
317 assert!(!config.enabled);
318 }
319
320 #[test]
321 fn test_rate_limit_config_custom() {
322 let config = RateLimitConfig::custom(50, 30);
323 assert_eq!(config.max_requests, 50);
324 assert_eq!(config.window, Duration::from_secs(30));
325 }
326
327 #[test]
328 fn test_rate_limiter_allows_requests() {
329 let limiter = RateLimiter::new(RateLimitConfig::custom(5, 60));
330 let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
331
332 for i in 0..5 {
334 let result = limiter.check(ip);
335 assert!(result.is_ok(), "Request {} should be allowed", i);
336 }
337 }
338
339 #[test]
340 fn test_rate_limiter_blocks_excess() {
341 let limiter = RateLimiter::new(RateLimitConfig::custom(3, 60));
342 let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
343
344 assert!(limiter.check(ip).is_ok());
346 assert!(limiter.check(ip).is_ok());
347 assert!(limiter.check(ip).is_ok());
348
349 let result = limiter.check(ip);
351 assert!(result.is_err());
352 }
353
354 #[test]
355 fn test_rate_limiter_different_ips() {
356 let limiter = RateLimiter::new(RateLimitConfig::custom(2, 60));
357 let ip1 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
358 let ip2 = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2));
359
360 assert!(limiter.check(ip1).is_ok());
362 assert!(limiter.check(ip1).is_ok());
363 assert!(limiter.check(ip1).is_err()); assert!(limiter.check(ip2).is_ok()); assert!(limiter.check(ip2).is_ok());
367 assert!(limiter.check(ip2).is_err()); }
369
370 #[test]
371 fn test_rate_limiter_disabled() {
372 let limiter = RateLimiter::disabled();
373 let ip = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
374
375 for _ in 0..100 {
377 assert!(limiter.check(ip).is_ok());
378 }
379 }
380
381 #[test]
382 fn test_rate_limiter_ipv6() {
383 let limiter = RateLimiter::new(RateLimitConfig::custom(2, 60));
384 let ip = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
385
386 assert!(limiter.check(ip).is_ok());
387 assert!(limiter.check(ip).is_ok());
388 assert!(limiter.check(ip).is_err());
389 }
390
391 #[test]
392 fn test_rate_limiter_stats() {
393 let limiter = RateLimiter::new(RateLimitConfig::custom(10, 30));
394 let ip = IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8));
395
396 limiter.check(ip).ok();
397
398 let stats = limiter.stats();
399 assert_eq!(stats.tracked_ips, 1);
400 assert_eq!(stats.max_requests, 10);
401 assert_eq!(stats.window_secs, 30);
402 assert!(stats.enabled);
403 }
404
405 #[test]
406 fn test_rate_limiter_remaining_count() {
407 let limiter = RateLimiter::new(RateLimitConfig::custom(5, 60));
408 let ip = IpAddr::V4(Ipv4Addr::new(172, 16, 0, 1));
409
410 assert_eq!(limiter.check(ip).unwrap(), 4); assert_eq!(limiter.check(ip).unwrap(), 3);
412 assert_eq!(limiter.check(ip).unwrap(), 2);
413 assert_eq!(limiter.check(ip).unwrap(), 1);
414 assert_eq!(limiter.check(ip).unwrap(), 0);
415 assert!(limiter.check(ip).is_err()); }
417}