velesdb_server/
rate_limit.rs1use std::sync::Arc;
9use std::time::Duration;
10use tower_governor::governor::{GovernorConfig, GovernorConfigBuilder};
11use tower_governor::key_extractor::SmartIpKeyExtractor;
12
13pub use tower_governor::GovernorLayer;
15
16type HeaderMiddleware = ::governor::middleware::StateInformationMiddleware;
18
19pub type RateLimitConfig = GovernorConfig<SmartIpKeyExtractor, HeaderMiddleware>;
21
22pub fn build_rate_limit_config(burst: u32) -> anyhow::Result<Arc<RateLimitConfig>> {
35 let mut builder = GovernorConfigBuilder::default();
36 builder.per_second(u64::from(burst));
37 builder.burst_size(burst);
38 let mut builder = builder.key_extractor(SmartIpKeyExtractor);
39 let mut builder = builder.use_headers();
40
41 let config = Arc::new(
42 builder
43 .finish()
44 .ok_or_else(|| anyhow::anyhow!("failed to build rate limiter configuration"))?,
45 );
46
47 spawn_limiter_cleanup(&config);
48
49 Ok(config)
50}
51
52fn spawn_limiter_cleanup(config: &Arc<RateLimitConfig>) {
54 let limiter = config.limiter().clone();
55 let interval = Duration::from_secs(60);
56 std::thread::spawn(move || loop {
57 std::thread::sleep(interval);
58 tracing::debug!("rate limiter cleanup: {} tracked IPs", limiter.len());
59 limiter.retain_recent();
60 });
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn test_build_rate_limit_config_succeeds() {
69 let config = build_rate_limit_config(100);
70 assert!(
71 config.is_ok(),
72 "governor config should build with burst=100"
73 );
74 }
75
76 #[test]
77 fn test_build_rate_limit_config_burst_one() {
78 let config = build_rate_limit_config(1);
79 assert!(config.is_ok(), "governor config should build with burst=1");
80 }
81
82 #[test]
87 fn test_rate_limit_replenishment_scales_with_burst() {
88 let config = build_rate_limit_config(100).expect("config should build");
92 let limiter = config.limiter();
93
94 let key = std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST);
96
97 assert!(
99 limiter.check_key(&key).is_ok(),
100 "first request should succeed"
101 );
102
103 assert!(
106 limiter.check_key(&key).is_ok(),
107 "second request should succeed (burst=100 allows many concurrent)"
108 );
109 }
110}