import time from collections import defaultdict from fastapi import HTTPException, Request from starlette.middleware.base import BaseHTTPMiddleware class RateLimitMiddleware(BaseHTTPMiddleware): def __init__(self, app, max_requests: int = 60, window_seconds: int = 60): super().__init__(app) self.max_requests = max_requests self.window_seconds = window_seconds self.requests: dict[str, list[float]] = defaultdict(list) async def dispatch(self, request: Request, call_next): if request.url.path in ("/api/health", "/static/", "/"): return await call_next(request) client_ip = request.client.host if request.client else "unknown" now = time.monotonic() cutoff = now - self.window_seconds self.requests[client_ip] = [t for t in self.requests[client_ip] if t > cutoff] if len(self.requests[client_ip]) >= self.max_requests: raise HTTPException(status_code=429, detail="Rate limit exceeded") self.requests[client_ip].append(now) return await call_next(request)