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}