use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use axum::response::Response;
use tower::{Layer, Service};
use super::{RateLimitError, RateLimiter};
type BoxFuture<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;
pub struct RateLimitLayer {
limiter: Arc<dyn RateLimiter>,
}
impl RateLimitLayer {
pub fn new(limiter: Arc<dyn RateLimiter>) -> Self {
Self { limiter }
}
}
impl<S> Layer<S> for RateLimitLayer {
type Service = RateLimitMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
RateLimitMiddleware {
inner,
limiter: self.limiter.clone(),
}
}
}
#[derive(Clone)]
pub struct RateLimitMiddleware<S> {
inner: S,
limiter: Arc<dyn RateLimiter>,
}
impl<S> Service<Request<Body>> for RateLimitMiddleware<S>
where
S: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
S::Future: Send,
S::Error: Send,
{
type Response = Response;
type Error = S::Error;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let limiter = self.limiter.clone();
let mut inner = self.inner.clone();
Box::pin(async move {
match limiter.check_request(&req).await {
Ok(()) => inner.call(req).await,
Err(err) => Ok(rate_limit_rejection_response(err)),
}
})
}
}
fn rate_limit_rejection_response(err: RateLimitError) -> Response {
let (status, body, retry_after_secs) = match &err {
RateLimitError::Exceeded { window_seconds, .. } => (
StatusCode::TOO_MANY_REQUESTS,
"Rate limit exceeded".to_string(),
Some(*window_seconds),
),
RateLimitError::Banned { reason } => (
StatusCode::FORBIDDEN,
format!("Banned: {}", reason),
None,
),
RateLimitError::CircuitOpen => (
StatusCode::SERVICE_UNAVAILABLE,
"Circuit breaker open".to_string(),
Some(60),
),
RateLimitError::QuotaExhausted { .. } => (
StatusCode::TOO_MANY_REQUESTS,
"Quota exhausted".to_string(),
None,
),
RateLimitError::Limiteron(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
"Internal rate limit error".to_string(),
None,
),
};
let mut resp = Response::new(Body::from(body));
*resp.status_mut() = status;
if let Some(secs) = retry_after_secs {
if let Ok(value) = secs.to_string().parse() {
resp.headers_mut().insert("Retry-After", value);
}
}
resp
}