1use std::future::{ready, Ready};
27use std::num::NonZeroU32;
28use std::rc::Rc;
29use std::sync::Arc;
30use std::time::Duration;
31
32use actix_web::body::{EitherBody, MessageBody};
33use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
34use actix_web::http::StatusCode;
35use actix_web::{Error, HttpResponse, ResponseError};
36use futures::future::LocalBoxFuture;
37use governor::clock::{Clock, DefaultClock};
38use governor::state::keyed::DefaultKeyedStateStore;
39use governor::{Quota, RateLimiter as GovernorRateLimiter};
40
41pub trait KeyExtractor: Clone + 'static {
48 type Key: Clone + Eq + std::hash::Hash + Send + Sync + 'static;
50 type KeyExtractionError: ResponseError + 'static;
53
54 fn extract(&self, req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError>;
56}
57
58#[derive(Debug, Clone, Copy)]
60pub struct SimpleKeyExtractionError(pub &'static str);
61
62impl SimpleKeyExtractionError {
63 pub const fn new(body: &'static str) -> Self {
64 Self(body)
65 }
66}
67
68impl std::fmt::Display for SimpleKeyExtractionError {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 write!(f, "SimpleKeyExtractionError")
71 }
72}
73
74impl std::error::Error for SimpleKeyExtractionError {}
75
76impl ResponseError for SimpleKeyExtractionError {
77 fn status_code(&self) -> StatusCode {
78 StatusCode::INTERNAL_SERVER_ERROR
79 }
80
81 fn error_response(&self) -> HttpResponse {
82 crate::error::json_error(self.status_code(), self.0)
83 }
84}
85
86type KeyedLimiter<Key> = GovernorRateLimiter<Key, DefaultKeyedStateStore<Key>, DefaultClock>;
87
88pub struct RateLimiterConfig<K: KeyExtractor> {
93 limiter: Arc<KeyedLimiter<K::Key>>,
94 key_extractor: K,
95}
96
97impl<K: KeyExtractor> Clone for RateLimiterConfig<K> {
98 fn clone(&self) -> Self {
99 Self {
100 limiter: self.limiter.clone(),
101 key_extractor: self.key_extractor.clone(),
102 }
103 }
104}
105
106impl<K: KeyExtractor> RateLimiterConfig<K> {
107 pub fn new(period: Duration, burst_size: u32, key_extractor: K) -> Self {
116 let burst = NonZeroU32::new(burst_size).expect("burst_size must be non-zero");
117 let quota = Quota::with_period(period)
118 .expect("period must be non-zero")
119 .allow_burst(burst);
120 Self {
121 limiter: Arc::new(GovernorRateLimiter::keyed(quota)),
122 key_extractor,
123 }
124 }
125}
126
127pub struct RateLimit<K: KeyExtractor> {
129 limiter: Arc<KeyedLimiter<K::Key>>,
130 key_extractor: K,
131}
132
133impl<K: KeyExtractor> RateLimit<K> {
134 pub fn new(config: &RateLimiterConfig<K>) -> Self {
136 Self {
137 limiter: config.limiter.clone(),
138 key_extractor: config.key_extractor.clone(),
139 }
140 }
141}
142
143impl<S, B, K> Transform<S, ServiceRequest> for RateLimit<K>
144where
145 K: KeyExtractor,
146 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
147 B: MessageBody + 'static,
148{
149 type Response = ServiceResponse<EitherBody<B>>;
150 type Error = Error;
151 type Transform = RateLimitMiddleware<S, K>;
152 type InitError = ();
153 type Future = Ready<Result<Self::Transform, Self::InitError>>;
154
155 fn new_transform(&self, service: S) -> Self::Future {
156 ready(Ok(RateLimitMiddleware {
157 service: Rc::new(service),
158 limiter: self.limiter.clone(),
159 key_extractor: self.key_extractor.clone(),
160 }))
161 }
162}
163
164pub struct RateLimitMiddleware<S, K: KeyExtractor> {
165 service: Rc<S>,
166 limiter: Arc<KeyedLimiter<K::Key>>,
167 key_extractor: K,
168}
169
170impl<S, B, K> Service<ServiceRequest> for RateLimitMiddleware<S, K>
171where
172 K: KeyExtractor,
173 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
174 B: MessageBody + 'static,
175{
176 type Response = ServiceResponse<EitherBody<B>>;
177 type Error = Error;
178 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
179
180 forward_ready!(service);
181
182 fn call(&self, req: ServiceRequest) -> Self::Future {
183 let key = match self.key_extractor.extract(&req) {
184 Ok(key) => key,
185 Err(err) => {
186 return Box::pin(async move { Err(err.into()) });
190 }
191 };
192
193 match self.limiter.check_key(&key) {
194 Ok(_) => {
195 let fut = self.service.call(req);
196 Box::pin(async move { fut.await.map(|res| res.map_into_left_body()) })
197 }
198 Err(negative) => {
199 let wait_time = negative
200 .wait_time_from(DefaultClock::default().now())
201 .as_secs();
202 let mut response = crate::error::json_error(
203 StatusCode::TOO_MANY_REQUESTS,
204 format!("Too many requests, retry in {wait_time}s"),
205 );
206 response.headers_mut().insert(
207 actix_web::http::header::RETRY_AFTER,
208 actix_web::http::header::HeaderValue::from(wait_time),
209 );
210 response.headers_mut().insert(
211 actix_web::http::header::HeaderName::from_static("x-ratelimit-after"),
212 actix_web::http::header::HeaderValue::from(wait_time),
213 );
214 let response = req.into_response(response).map_into_right_body();
215 Box::pin(async move { Ok(response) })
216 }
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use actix_web::http::StatusCode as HttpStatusCode;
225 use actix_web::{test, web, App, HttpResponse as Resp};
226 use std::net::{IpAddr, Ipv4Addr};
227
228 #[derive(Clone)]
233 struct FixedKeyExtractor(IpAddr);
234
235 impl KeyExtractor for FixedKeyExtractor {
236 type Key = IpAddr;
237 type KeyExtractionError = SimpleKeyExtractionError;
238
239 fn extract(&self, _req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError> {
240 Ok(self.0)
241 }
242 }
243
244 #[actix_web::test]
245 async fn burst_then_429_with_retry_after_header() {
246 let key = IpAddr::V4(Ipv4Addr::new(10, 1, 1, 1));
247 let conf = RateLimiterConfig::new(Duration::from_millis(100), 3, FixedKeyExtractor(key));
248 let app = test::init_service(
249 App::new()
250 .wrap(RateLimit::new(&conf))
251 .route("/", web::get().to(|| async { Resp::Ok().finish() })),
252 )
253 .await;
254
255 for _ in 0..3 {
257 let res =
258 test::call_service(&app, test::TestRequest::get().uri("/").to_request()).await;
259 assert_eq!(res.status(), HttpStatusCode::OK);
260 }
261
262 let res = test::call_service(&app, test::TestRequest::get().uri("/").to_request()).await;
264 assert_eq!(res.status(), HttpStatusCode::TOO_MANY_REQUESTS);
265 assert!(
266 res.headers().contains_key("retry-after"),
267 "a 429 must carry Retry-After"
268 );
269 assert!(
270 res.headers().contains_key("x-ratelimit-after"),
271 "a 429 must carry x-ratelimit-after"
272 );
273 let body: serde_json::Value = test::read_body_json(res).await;
274 assert_eq!(body["error"]["type"], "api_error");
275 assert!(body["error"]["message"]
276 .as_str()
277 .is_some_and(|message| message.starts_with("Too many requests, retry in ")));
278 }
279
280 #[actix_web::test]
281 async fn refills_after_period_elapses() {
282 let key = IpAddr::V4(Ipv4Addr::new(10, 1, 1, 2));
283 let conf = RateLimiterConfig::new(Duration::from_millis(20), 1, FixedKeyExtractor(key));
285 let app = test::init_service(
286 App::new()
287 .wrap(RateLimit::new(&conf))
288 .route("/", web::get().to(|| async { Resp::Ok().finish() })),
289 )
290 .await;
291
292 let res = test::call_service(&app, test::TestRequest::get().uri("/").to_request()).await;
293 assert_eq!(res.status(), HttpStatusCode::OK);
294
295 let res = test::call_service(&app, test::TestRequest::get().uri("/").to_request()).await;
296 assert_eq!(
297 res.status(),
298 HttpStatusCode::TOO_MANY_REQUESTS,
299 "bucket must be empty immediately after the burst"
300 );
301
302 tokio::time::sleep(Duration::from_millis(60)).await;
303
304 let res = test::call_service(&app, test::TestRequest::get().uri("/").to_request()).await;
305 assert_eq!(
306 res.status(),
307 HttpStatusCode::OK,
308 "the bucket must have refilled after the period elapses"
309 );
310 }
311
312 #[actix_web::test]
313 async fn extraction_failure_returns_extractors_error_response() {
314 #[derive(Clone)]
315 struct AlwaysFailsExtractor;
316
317 impl KeyExtractor for AlwaysFailsExtractor {
318 type Key = ();
319 type KeyExtractionError = SimpleKeyExtractionError;
320
321 fn extract(
322 &self,
323 _req: &ServiceRequest,
324 ) -> Result<Self::Key, Self::KeyExtractionError> {
325 Err(SimpleKeyExtractionError::new("no key for you"))
326 }
327 }
328
329 let conf = RateLimiterConfig::new(Duration::from_secs(1), 1, AlwaysFailsExtractor);
330 let app = test::init_service(
331 App::new()
332 .wrap(RateLimit::new(&conf))
333 .route("/", web::get().to(|| async { Resp::Ok().finish() })),
334 )
335 .await;
336
337 let err = test::try_call_service(&app, test::TestRequest::get().uri("/").to_request())
344 .await
345 .expect_err("extraction failure must reach the caller as an Err");
346 let response = err.error_response();
347 assert_eq!(response.status(), HttpStatusCode::INTERNAL_SERVER_ERROR);
348 let body = actix_web::body::to_bytes(response.into_body())
349 .await
350 .unwrap();
351 let body: serde_json::Value = serde_json::from_slice(&body).unwrap();
352 assert_eq!(body["error"]["message"], "no key for you");
353 assert_eq!(body["error"]["type"], "api_error");
354 }
355}