1use std::fmt;
2use std::future::Future;
3use std::net::IpAddr;
4use std::pin::Pin;
5use std::sync::Arc;
6use std::task::{Context, Poll};
7
8use http::{Extensions, HeaderMap, HeaderValue, StatusCode};
9use tower::Service;
10use tower_layer::Layer;
11
12use crate::RateLimiter;
13use crate::backend::RateLimitBackend;
14use crate::client_ip::{ClientIpConfig, MissingClientIdentity, MissingClientPolicy};
15use crate::quota::Quota;
16
17use std::fmt::Write as _;
18
19struct SliceWriter<'a> {
26 buf: &'a mut [u8],
27 len: usize,
28}
29
30impl fmt::Write for SliceWriter<'_> {
31 fn write_str(&mut self, s: &str) -> fmt::Result {
32 let end = self.len + s.len();
33 let slot = self.buf.get_mut(self.len..end).ok_or(fmt::Error)?;
34 slot.copy_from_slice(s.as_bytes());
35 self.len = end;
36 Ok(())
37 }
38}
39
40struct KeyBuf([u8; 45]);
43
44impl KeyBuf {
45 fn format_ip(&mut self, ip: IpAddr) -> &str {
47 let mut writer = SliceWriter {
48 buf: &mut self.0,
49 len: 0,
50 };
51 let _ = write!(writer, "{ip}");
54 let len = writer.len;
55 let bytes = self.0.get(..len).unwrap_or(&[]);
57 #[allow(clippy::incompatible_msrv)]
61 str::from_utf8(bytes).unwrap_or_default()
62 }
63}
64
65fn u64_header_value(v: u64) -> Option<HeaderValue> {
68 let mut buf = [0u8; 20];
69 let len = {
70 let mut writer = SliceWriter {
71 buf: &mut buf,
72 len: 0,
73 };
74 let _ = write!(writer, "{v}");
75 writer.len
76 };
77 HeaderValue::from_bytes(buf.get(..len)?).ok()
78}
79
80pub type KeyExtractor = Arc<dyn Fn(&HeaderMap, &Extensions) -> String + Send + Sync>;
87
88#[derive(Clone)]
90enum KeySource {
91 ClientIp {
93 config: ClientIpConfig,
94 missing_policy: MissingClientPolicy,
95 },
96 Custom(KeyExtractor),
98}
99
100#[derive(Clone)]
113pub struct RateLimitLayer<B: RateLimitBackend> {
114 limiter: RateLimiter<B>,
115 key_source: Arc<KeySource>,
119}
120
121impl<B: RateLimitBackend> RateLimitLayer<B> {
122 pub fn new(quota: Quota, backend: B) -> Self {
128 Self {
129 limiter: RateLimiter::new(quota, backend),
130 key_source: Arc::new(KeySource::ClientIp {
131 config: ClientIpConfig::default(),
132 missing_policy: MissingClientPolicy::default(),
133 }),
134 }
135 }
136
137 pub fn with_client_ip(mut self, config: ClientIpConfig) -> Self {
141 let missing_policy = match self.key_source.as_ref() {
142 KeySource::ClientIp { missing_policy, .. } => missing_policy.clone(),
143 KeySource::Custom(_) => MissingClientPolicy::default(),
144 };
145 self.key_source = Arc::new(KeySource::ClientIp {
146 config,
147 missing_policy,
148 });
149 self
150 }
151
152 pub fn with_missing_client_policy(mut self, policy: MissingClientPolicy) -> Self {
157 let mut source = match Arc::try_unwrap(self.key_source) {
161 Ok(source) => source,
162 Err(shared) => (*shared).clone(),
163 };
164 if let KeySource::ClientIp { missing_policy, .. } = &mut source {
165 *missing_policy = policy;
166 }
167 self.key_source = Arc::new(source);
168 self
169 }
170
171 pub fn with_key_extractor(mut self, extractor: KeyExtractor) -> Self {
174 self.key_source = Arc::new(KeySource::Custom(extractor));
175 self
176 }
177}
178
179impl<S, B> Layer<S> for RateLimitLayer<B>
180where
181 B: RateLimitBackend + Clone,
182{
183 type Service = RateLimitService<S, B>;
184
185 fn layer(&self, inner: S) -> Self::Service {
186 RateLimitService {
187 inner,
188 limiter: self.limiter.clone(),
189 key_source: self.key_source.clone(),
190 }
191 }
192}
193
194#[derive(Clone)]
199pub struct RateLimitService<S, B: RateLimitBackend> {
200 inner: S,
201 limiter: RateLimiter<B>,
202 key_source: Arc<KeySource>,
203}
204
205impl<S, ReqBody, B> Service<http::Request<ReqBody>> for RateLimitService<S, B>
206where
207 S: Service<http::Request<ReqBody>, Response = http::Response<ReqBody>> + Clone + Send + 'static,
208 S::Future: Send + 'static,
209 B: RateLimitBackend + Clone,
210 ReqBody: Default + Send + 'static,
214{
215 type Response = http::Response<ReqBody>;
216 type Error = S::Error;
217 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
218
219 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
220 self.inner.poll_ready(cx)
221 }
222
223 fn call(&mut self, req: http::Request<ReqBody>) -> Self::Future {
224 let key_source = Arc::clone(&self.key_source);
225 let limiter = self.limiter.clone();
226 let mut inner = self.inner.clone();
227
228 Box::pin(async move {
229 let mut key_buf = KeyBuf([0u8; 45]);
234 let custom_key: Option<String> = match key_source.as_ref() {
235 KeySource::Custom(extract) => Some(extract(req.headers(), req.extensions())),
236 _ => None,
237 };
238
239 let key: &str = match key_source.as_ref() {
240 KeySource::ClientIp {
241 config,
242 missing_policy,
243 } => {
244 let peer = crate::client_ip::peer_ip_from_extensions(req.extensions());
245 match crate::client_ip::resolve_client_identity(req.headers(), peer, config) {
246 Ok(resolved) => key_buf.format_ip(resolved.ip),
247 Err(MissingClientIdentity) => match missing_policy {
248 MissingClientPolicy::Reject => {
251 let mut response = http::Response::new(ReqBody::default());
252 *response.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
253 return Ok(response);
254 }
255 MissingClientPolicy::FallbackKey(key) => key.as_ref(),
256 },
257 }
258 }
259 KeySource::Custom(_) => custom_key.as_deref().unwrap_or_default(),
260 };
261
262 let result = limiter.check(key).await;
263
264 if !result.allowed {
265 let mut response = http::Response::new(ReqBody::default());
266 *response.status_mut() = StatusCode::TOO_MANY_REQUESTS;
267 response.headers_mut().insert(
268 http::header::HeaderName::from_static("retry-after"),
269 http::header::HeaderValue::from_static("1"),
270 );
271 return Ok(response);
272 }
273
274 let mut response = inner.call(req).await?;
275 let headers = response.headers_mut();
276 insert_rate_limit_headers(headers, &result);
277 Ok(response)
278 })
279 }
280}
281
282fn insert_rate_limit_headers(headers: &mut HeaderMap, result: &crate::metrics::RateLimitResult) {
283 if let Some(val) = u64_header_value(result.limit) {
287 headers.insert("X-RateLimit-Limit", val);
288 }
289 if let Some(val) = u64_header_value(result.remaining) {
290 headers.insert("X-RateLimit-Remaining", val);
291 }
292 let reset_secs = result
293 .reset_at
294 .checked_duration_since(std::time::Instant::now())
295 .unwrap_or_default()
296 .as_secs();
297 if let Some(val) = u64_header_value(reset_secs) {
298 headers.insert("X-RateLimit-Reset", val);
299 }
300}
301
302#[allow(
306 clippy::unwrap_used,
307 clippy::expect_used,
308 clippy::indexing_slicing,
309 clippy::panic
310)]
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::metrics::RateLimitResult;
315 use std::convert::Infallible;
316 use std::sync::Arc;
317 use std::time::Instant;
318
319 #[derive(Clone)]
321 struct AlwaysAllowBackend;
322
323 impl RateLimitBackend for AlwaysAllowBackend {
324 async fn check(&self, _key: &str, quota: &Quota) -> RateLimitResult {
325 RateLimitResult {
326 allowed: true,
327 remaining: u64::from(quota.burst) - 1,
328 reset_at: Instant::now() + quota.interval(),
329 limit: u64::from(quota.burst),
330 retry_after: None,
331 }
332 }
333 }
334
335 #[derive(Clone)]
338 struct PendingService;
339
340 impl Service<http::Request<()>> for PendingService {
341 type Response = http::Response<()>;
342 type Error = Infallible;
343 type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
344
345 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
346 Poll::Pending
347 }
348
349 fn call(&mut self, _req: http::Request<()>) -> Self::Future {
350 std::future::ready(Ok(http::Response::new(())))
351 }
352 }
353
354 fn layer_service() -> RateLimitService<PendingService, AlwaysAllowBackend> {
355 RateLimitLayer::new(Quota::per_second(10), AlwaysAllowBackend)
356 .with_key_extractor(Arc::new(|_, _| "test-key".to_string()))
357 .layer(PendingService)
358 }
359
360 #[test]
361 fn poll_ready_delegates_to_inner_service() {
362 let mut svc = layer_service();
363 let cx = &mut Context::from_waker(std::task::Waker::noop());
364 assert!(
365 matches!(svc.poll_ready(cx), Poll::Pending),
366 "readiness must come from the inner service"
367 );
368 }
369
370 #[tokio::test]
371 async fn inserts_rate_limit_headers_on_allowed_response() {
372 let mut svc = layer_service();
373 let response = svc
374 .call(http::Request::builder().body(()).unwrap())
375 .await
376 .unwrap();
377
378 assert_eq!(response.status(), StatusCode::OK);
379 let headers = response.headers();
380 assert_eq!(headers.get("X-RateLimit-Limit").unwrap(), "10");
381 assert_eq!(headers.get("X-RateLimit-Remaining").unwrap(), "9");
382 assert!(headers.contains_key("X-RateLimit-Reset"));
383 }
384}