voiceforge-universal / backend /app /core /middleware.py
creator-o1
Initial commit: Complete VoiceForge Enterprise Speech AI Platform
d00203b
"""
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)