arcly_http/resilience/
rate_limit.rs1use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15use crate::auth::guards::Guard;
16use crate::web::{Error, RequestContext};
17
18pub struct RateLimit {
21 state: AtomicU64, window_secs: u32,
23 max_per_window: u32,
24}
25
26impl RateLimit {
27 pub const fn new(max_per_window: u32, window_secs: u32) -> Self {
28 Self {
29 state: AtomicU64::new(0),
30 window_secs,
31 max_per_window,
32 }
33 }
34
35 fn now_secs() -> u32 {
36 SystemTime::now()
37 .duration_since(UNIX_EPOCH)
38 .map(|d| d.as_secs() as u32)
39 .unwrap_or(0)
40 }
41}
42
43impl Guard for RateLimit {
44 fn check(&self, _ctx: &RequestContext) -> Result<(), Error> {
45 let now = Self::now_secs();
46 loop {
47 let cur = self.state.load(Relaxed);
48 let (start, count) = ((cur >> 32) as u32, cur as u32);
49 let (new_start, new_count) = if now.saturating_sub(start) >= self.window_secs {
50 (now, 1)
51 } else {
52 (start, count.saturating_add(1))
53 };
54 if new_count > self.max_per_window {
55 return Err(Error::TooManyRequests);
56 }
57 let next = ((new_start as u64) << 32) | (new_count as u64);
58 if self
59 .state
60 .compare_exchange(cur, next, Relaxed, Relaxed)
61 .is_ok()
62 {
63 return Ok(());
64 }
65 }
66 }
67}