| """
|
| Rate limiting middleware using SlowAPI.
|
|
|
| Provides request rate limiting per API key.
|
| """
|
|
|
| from slowapi import Limiter
|
| from slowapi.util import get_remote_address
|
| from starlette.requests import Request
|
|
|
| from app.config import get_settings
|
| from app.utils.logger import get_logger
|
|
|
| logger = get_logger(__name__)
|
|
|
|
|
| def get_api_key_or_ip(request: Request) -> str:
|
| """
|
| Extract rate limit key from request.
|
|
|
| Uses API key if present, otherwise falls back to IP address.
|
|
|
| Args:
|
| request: Incoming request
|
|
|
| Returns:
|
| Rate limit key (API key or IP)
|
| """
|
| api_key = request.headers.get("x-api-key")
|
|
|
| if api_key:
|
|
|
| return f"key:{api_key}"
|
|
|
|
|
| return f"ip:{get_remote_address(request)}"
|
|
|
|
|
| def get_limiter() -> Limiter:
|
| """
|
| Create and configure rate limiter.
|
|
|
| Returns:
|
| Configured Limiter instance
|
| """
|
| settings = get_settings()
|
|
|
|
|
| default_limit = f"{settings.RATE_LIMIT_REQUESTS}/minute"
|
|
|
| return Limiter(
|
| key_func=get_api_key_or_ip,
|
| default_limits=[default_limit],
|
|
|
| )
|
|
|
|
|
|
|
| limiter = get_limiter()
|
|
|
|
|
| def rate_limit_exceeded_handler(request: Request, exc: Exception):
|
| """
|
| Handle rate limit exceeded errors.
|
|
|
| Args:
|
| request: Request that exceeded the limit
|
| exc: Rate limit exception
|
|
|
| Returns:
|
| JSON response with 429 status
|
| """
|
| from starlette.responses import JSONResponse
|
|
|
| logger.warning(
|
| "Rate limit exceeded",
|
| path=request.url.path,
|
| client=get_api_key_or_ip(request),
|
| )
|
|
|
| return JSONResponse(
|
| status_code=429,
|
| content={
|
| "status": "error",
|
| "message": "Rate limit exceeded. Please try again later.",
|
| },
|
| headers={
|
| "Retry-After": "60",
|
| },
|
| )
|
|
|