Skip to main content

actix_web_ratelimit/store/
memory_store.rs

1use dashmap::DashMap;
2use std::{sync::Arc, time::Instant};
3
4use crate::{config::RateLimitConfig, store::RateLimitStore};
5
6/// In-memory implementation of [`RateLimitStore`] using DashMap for concurrent access.
7///
8/// This store uses a thread-safe HashMap (DashMap) to store request timestamps
9/// for each client identifier. It's suitable for single-instance applications
10/// where rate limiting data doesn't need to be shared across multiple processes.
11///
12/// # Performance
13///
14/// - Fast access with O(1) lookup time
15/// - Thread-safe concurrent operations
16/// - Memory usage grows with the number of unique clients
17///
18/// # Limitations
19///
20/// - Data is lost on application restart
21/// - Not suitable for distributed systems
22/// - Memory usage can grow if clients are not cleaned up
23pub struct MemoryStore {
24    /// Thread-safe map storing client identifiers and their request timestamps
25    pub store: DashMap<String, Vec<Instant>>,
26}
27
28impl MemoryStore {
29    /// Creates a new [`MemoryStore`] instance with an empty DashMap.
30    ///
31    /// # Returns
32    ///
33    /// A new `MemoryStore` instance ready for use.
34    ///
35    /// # Example
36    ///
37    /// ```rust
38    /// use actix_web_ratelimit::store::MemoryStore;
39    /// use std::sync::Arc;
40    ///
41    /// let store = Arc::new(MemoryStore::new());
42    /// ```
43    pub fn new() -> Self {
44        Self {
45            store: DashMap::new(),
46        }
47    }
48}
49
50/// Default implementation that creates a new [`MemoryStore`] instance.
51///
52/// This is equivalent to calling [`MemoryStore::new()`].
53impl Default for MemoryStore {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl RateLimitStore for MemoryStore {
60    /// Checks if the client has exceeded the rate limit and records the current request.
61    ///
62    /// This method implements the sliding window algorithm:
63    /// 1. Gets or creates an entry for the client key
64    /// 2. Removes expired timestamps outside the time window
65    /// 3. Checks if the remaining request count exceeds the limit
66    /// 4. If not exceeded, records the current timestamp
67    ///
68    /// # Arguments
69    ///
70    /// * `key` - Client identifier (typically IP address)
71    /// * `config` - Rate limiting configuration
72    ///
73    /// # Returns
74    ///
75    /// `true` if the client has exceeded the rate limit, `false` otherwise
76    fn is_limited(&self, key: &str, config: &RateLimitConfig) -> bool {
77        let now = Instant::now();
78        let mut entry = self.store.entry(key.to_string()).or_default();
79        let timestamps = entry.value_mut();
80
81        // Keep only timestamps within the time window
82        timestamps.retain(|&t| now.duration_since(t) <= config.window_secs);
83        if timestamps.len() > config.max_requests {
84            return true;
85        }
86
87        timestamps.push(now);
88        false
89    }
90}
91
92/// Implementation of [`RateLimitStore`] for `Arc<MemoryStore>` to enable shared ownership.
93///
94/// This allows the same `MemoryStore` instance to be used across multiple threads
95/// and middleware instances safely.
96impl RateLimitStore for Arc<MemoryStore> {
97    /// Delegates to the underlying `MemoryStore` implementation.
98    fn is_limited(&self, key: &str, config: &RateLimitConfig) -> bool {
99        (**self).is_limited(key, config)
100    }
101}