Governor Rate Limit Middleware
Apply the governor quota-based limiter with per-key direct limiters, burst capacity, and 429 responses carrying retry timing.
Code
use governor::{
clock::DefaultClock, middleware::NoOpMiddleware, state::direct::NotKeyed,
state::InMemoryState, Quota, RateLimiter,
};
use std::num::NonZeroU32;
use std::sync::Arc;
use std::time::Duration;
type SharedLimiter = RateLimiter<
NotKeyed,
InMemoryState,
DefaultClock,
NoOpMiddleware,
>;
#[derive(Clone)]
struct ApiGate {
limiter: Arc<SharedLimiter>,
}
impl ApiGate {
fn new(per_minute: u32) -> Self {
let quota = Quota::per_minute(NonZeroU32::new(per_minute).expect("positive"));
Self {
limiter: Arc::new(RateLimiter::direct(quota)),
}
}
fn check(&self) -> Result<(), Duration> {
match self.limiter.check() {
Ok(_) => Ok(()),
Err(negative) => Err(negative.wait_time_from_now()),
}
}
}
async fn handle_request(
gate: &ApiGate,
route: &str,
) -> Result<String, (u16, String)> {
if let Err(wait) = gate.check() {
let retry_after = wait.as_secs().max(1);
return Err((
429,
format!("rate limited, retry after {retry_after}s"),
));
}
Ok(format!("{route} served"))
}
#[tokio::main]
async fn main() {
let gate = ApiGate::new(5);
let mut results = Vec::new();
for i in 0..8 {
let outcome = handle_request(&gate, "/search").await;
let rendered = match outcome {
Ok(body) => body,
Err((status, message)) => format!("HTTP {status}: {message}"),
};
results.push(format!("req {i}: {rendered}"));
}
for line in results {
println!("{line}");
}
}Report a problem with this exampleOpens a pre-filled issue on GitHub.