actix_web_ratelimit/config.rs
1use actix_web::{HttpResponse, dev::ServiceRequest};
2use std::time::Duration;
3
4/// Configuration for rate limiting middleware.
5///
6/// This struct contains all the parameters needed to configure rate limiting behavior,
7/// including request limits, time windows, and callback functions for client identification
8/// and rate limit exceeded handling.
9///
10/// # Examples
11///
12/// ```rust
13/// use actix_web::HttpResponse;
14/// use actix_web_ratelimit::config::RateLimitConfig;
15///
16/// // Basic configuration
17/// let config = RateLimitConfig::default()
18/// .max_requests(100)
19/// .window_secs(3600);
20///
21/// // Advanced configuration with custom handlers
22/// let config = RateLimitConfig::default()
23/// .max_requests(10)
24/// .window_secs(60)
25/// .id(|req| {
26/// // Custom client identification based on API key
27/// req.headers()
28/// .get("X-API-Key")
29/// .and_then(|h| h.to_str().ok())
30/// .unwrap_or("anonymous")
31/// .to_string()
32/// })
33/// .exceeded(|id, _config, _req| {
34/// // Custom rate limit exceeded response
35/// HttpResponse::TooManyRequests()
36/// .body(format!("Rate limit exceeded for client: {}", id))
37/// });
38/// ```
39#[derive(Clone)]
40pub struct RateLimitConfig {
41 /// Maximum number of requests allowed within the time window
42 pub max_requests: usize,
43 /// Duration of the sliding time window
44 pub window_secs: Duration,
45 /// Function to extract client identifier from the request.
46 /// Typically extracts IP address, but can be customized for API keys, user IDs, etc.
47 pub get_id: fn(req: &ServiceRequest) -> String,
48 /// Function called when rate limit is exceeded.
49 /// Receives the client ID, configuration, and request, returns the HTTP response.
50 pub on_exceed: fn(id: &String, config: &RateLimitConfig, req: &ServiceRequest) -> HttpResponse,
51}
52
53impl Default for RateLimitConfig {
54 /// Creates a default rate limiting configuration.
55 ///
56 /// # Default Values
57 ///
58 /// - `max_requests`: 10 requests
59 /// - `window_secs`: 100 seconds
60 /// - `get_id`: Extracts real IP address from connection info
61 /// - `on_exceed`: Returns HTTP 429 "Too Many Requests" with plain text body
62 ///
63 /// # Example
64 ///
65 /// ```rust
66 /// use actix_web_ratelimit::config::RateLimitConfig;
67 ///
68 /// let config = RateLimitConfig::default();
69 /// assert_eq!(config.max_requests, 10);
70 /// ```
71 fn default() -> Self {
72 Self {
73 max_requests: 10,
74 window_secs: Duration::from_secs(100),
75 get_id: |req| {
76 req.connection_info()
77 .realip_remote_addr()
78 .unwrap_or("-")
79 .to_string()
80 },
81 on_exceed: |_id, _config, _req| {
82 HttpResponse::TooManyRequests()
83 .body("Too many requests")
84 },
85 }
86 }
87}
88
89impl RateLimitConfig {
90 /// Sets the maximum number of requests allowed within the time window.
91 ///
92 /// # Arguments
93 ///
94 /// * `value` - Maximum number of requests (must be > 0)
95 ///
96 /// # Example
97 ///
98 /// ```rust
99 /// use actix_web_ratelimit::config::RateLimitConfig;
100 ///
101 /// let config = RateLimitConfig::default().max_requests(100);
102 /// ```
103 pub fn max_requests(mut self, value: usize) -> Self {
104 self.max_requests = value;
105 Self { ..self }
106 }
107
108 /// Sets the time window duration in seconds for the sliding window algorithm.
109 ///
110 /// # Arguments
111 ///
112 /// * `value` - Time window duration in seconds
113 ///
114 /// # Example
115 ///
116 /// ```rust
117 /// use actix_web_ratelimit::config::RateLimitConfig;
118 ///
119 /// // Allow 100 requests per hour
120 /// let config = RateLimitConfig::default()
121 /// .max_requests(100)
122 /// .window_secs(3600);
123 /// ```
124 pub fn window_secs(mut self, value: u64) -> Self {
125 self.window_secs = Duration::from_secs(value);
126 Self { ..self }
127 }
128
129 /// Sets a custom function to extract client identifier from requests.
130 ///
131 /// By default, the middleware uses the client's IP address as identifier.
132 /// This method allows customization based on headers, authentication, etc.
133 ///
134 /// # Arguments
135 ///
136 /// * `fn_id` - Function that takes a `ServiceRequest` and returns a client identifier string
137 ///
138 /// # Examples
139 ///
140 /// ```rust
141 /// use actix_web_ratelimit::config::RateLimitConfig;
142 ///
143 /// // Rate limit by API key
144 /// let config = RateLimitConfig::default()
145 /// .id(|req| {
146 /// req.headers()
147 /// .get("X-API-Key")
148 /// .and_then(|h| h.to_str().ok())
149 /// .unwrap_or("anonymous")
150 /// .to_string()
151 /// });
152 ///
153 /// // Rate limit by custom header
154 /// let config = RateLimitConfig::default()
155 /// .id(|req| {
156 /// // Extract user ID from custom header
157 /// req.headers()
158 /// .get("X-User-ID")
159 /// .and_then(|h| h.to_str().ok())
160 /// .unwrap_or("guest")
161 /// .to_string()
162 /// });
163 /// ```
164 pub fn id(mut self, fn_id: fn(req: &ServiceRequest) -> String) -> Self {
165 self.get_id = fn_id;
166 Self { ..self }
167 }
168
169 /// Sets a custom function to handle rate limit exceeded scenarios.
170 ///
171 /// By default, returns HTTP 429 with "Too many requests" message.
172 /// This method allows customization of the response format, headers, etc.
173 ///
174 /// # Arguments
175 ///
176 /// * `fn_exceed` - Function that takes client ID, config, and request, returns HTTP response
177 ///
178 /// # Examples
179 ///
180 /// ```rust
181 /// use actix_web::HttpResponse;
182 /// use actix_web_ratelimit::config::RateLimitConfig;
183 ///
184 /// // Custom error response with details
185 /// let config = RateLimitConfig::default()
186 /// .exceeded(|id, config, _req| {
187 /// HttpResponse::TooManyRequests()
188 /// .body(format!(
189 /// "Rate limit exceeded for client: {}. Limit: {} requests per {} seconds.",
190 /// id,
191 /// config.max_requests,
192 /// config.window_secs.as_secs()
193 /// ))
194 /// });
195 ///
196 /// // Custom headers and retry-after
197 /// let config = RateLimitConfig::default()
198 /// .exceeded(|_id, config, _req| {
199 /// HttpResponse::TooManyRequests()
200 /// .append_header(("Retry-After", config.window_secs.as_secs()))
201 /// .append_header(("X-RateLimit-Limit", config.max_requests))
202 /// .body("Rate limit exceeded. Please try again later.")
203 /// });
204 /// ```
205 pub fn exceeded(
206 mut self,
207 fn_exceed: fn(id: &String, config: &RateLimitConfig, req: &ServiceRequest) -> HttpResponse,
208 ) -> Self {
209 self.on_exceed = fn_exceed;
210 Self { ..self }
211 }
212}