Spaces:
Sleeping
Sleeping
| """ | |
| Rate Limiting Middleware | |
| Uses Redis to track and limit request rates per IP address. | |
| Pure ASGI implementation to avoid BaseHTTPMiddleware issues. | |
| """ | |
| import time | |
| import redis | |
| from starlette.responses import JSONResponse | |
| from starlette.types import ASGIApp, Scope, Receive, Send | |
| from ..core.config import get_settings | |
| settings = get_settings() | |
| class RateLimitMiddleware: | |
| def __init__(self, app: ASGIApp): | |
| self.app = app | |
| # Hardcoded or from settings (bypassing constructor arg issue) | |
| self.requests_per_minute = 60 | |
| self.window_size = 60 # seconds | |
| # Connect to Redis | |
| try: | |
| self.redis_client = redis.from_url(settings.redis_url) | |
| except Exception as e: | |
| print(f"⚠️ Rate limiter disabled: Could not connect to Redis ({e})") | |
| self.redis_client = None | |
| async def __call__(self, scope: Scope, receive: Receive, send: Send): | |
| # Skip if not HTTP | |
| if scope["type"] != "http": | |
| await self.app(scope, receive, send) | |
| return | |
| # Skip rate limiting for non-API routes or if Redis is down | |
| path = scope.get("path", "") | |
| if not path.startswith("/api/") or self.redis_client is None: | |
| await self.app(scope, receive, send) | |
| return | |
| # Get client IP | |
| client = scope.get("client") | |
| client_ip = client[0] if client else "unknown" | |
| key = f"rate_limit:{client_ip}" | |
| try: | |
| # Simple fixed window counter | |
| current_count = self.redis_client.incr(key) | |
| # Set expiry on first request | |
| if current_count == 1: | |
| self.redis_client.expire(key, self.window_size) | |
| if current_count > self.requests_per_minute: | |
| response = JSONResponse( | |
| status_code=429, | |
| content={ | |
| "detail": "Too many requests", | |
| "retry_after": self.window_size | |
| }, | |
| headers={"Retry-After": str(self.window_size)} | |
| ) | |
| await response(scope, receive, send) | |
| return | |
| except redis.RedisError: | |
| # Fail open if Redis has issues during request | |
| pass | |
| await self.app(scope, receive, send) | |