RAGChatbot / api /middleware.py
Shurem's picture
Add application file
9c13f8a
Raw
History Blame Contribute Delete
4.29 kB
from fastapi import Request, HTTPException, status
from fastapi.responses import JSONResponse
from typing import Dict, Optional
from datetime import datetime, timedelta
import time
import logging
logger = logging.getLogger(__name__)
class RateLimiter:
def __init__(self, requests_limit: int = 100, window_size: int = 60):
"""
Initialize rate limiter
:param requests_limit: Number of requests allowed per window
:param window_size: Time window in seconds
"""
self.requests_limit = requests_limit
self.window_size = window_size
self.requests: Dict[str, list] = {} # user_id -> list of request timestamps
def is_allowed(self, user_id: str) -> tuple[bool, Optional[int]]:
"""
Check if request is allowed for user
:param user_id: User identifier
:return: (is_allowed, seconds_to_wait)
"""
now = time.time()
window_start = now - self.window_size
if user_id not in self.requests:
self.requests[user_id] = []
# Remove old requests outside the window
self.requests[user_id] = [req_time for req_time in self.requests[user_id] if req_time > window_start]
# Check if limit is exceeded
if len(self.requests[user_id]) >= self.requests_limit:
# Calculate wait time
oldest_request = min(self.requests[user_id])
wait_time = int(oldest_request + self.window_size - now)
return False, max(1, wait_time)
# Add current request
self.requests[user_id].append(now)
return True, None
# Global rate limiter instance
rate_limiter = RateLimiter(
requests_limit=int(__import__('os').environ.get('RATE_LIMIT_REQUESTS', '100')),
window_size=int(__import__('os').environ.get('RATE_LIMIT_WINDOW', '60'))
)
async def rate_limit_middleware(request: Request, call_next):
"""
Rate limiting middleware
"""
# Extract user ID from headers or session
user_id = request.headers.get('x-user-id') or 'anonymous'
# If API key is available, use it as identifier
auth_header = request.headers.get('authorization', '')
if auth_header.startswith('Bearer '):
user_id = auth_header.split(' ')[1][:12] # Use first 12 chars of API key as identifier
is_allowed, wait_time = rate_limiter.is_allowed(user_id)
if not is_allowed:
return JSONResponse(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
content={
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": f"Rate limit exceeded. Please try again in {wait_time} seconds.",
"details": {
"user_id": user_id,
"requests_limit": rate_limiter.requests_limit,
"window_size": rate_limiter.window_size,
"wait_time_seconds": wait_time
},
"timestamp": datetime.utcnow().isoformat()
}
}
)
# Add rate limit headers to response
response = await call_next(request)
# Calculate remaining requests
now = time.time()
window_start = now - rate_limiter.window_size
if user_id in rate_limiter.requests:
current_requests = [req_time for req_time in rate_limiter.requests[user_id] if req_time > window_start]
remaining = rate_limiter.requests_limit - len(current_requests)
else:
remaining = rate_limiter.requests_limit
response.headers["X-RateLimit-Remaining"] = str(remaining)
response.headers["X-RateLimit-Limit"] = str(rate_limiter.requests_limit)
response.headers["X-RateLimit-Reset"] = str(int(now + rate_limiter.window_size))
return response
def get_client_ip(request: Request) -> str:
"""
Get client IP address from request
"""
# Check for forwarded headers first (for reverse proxies)
forwarded_for = request.headers.get("x-forwarded-for")
if forwarded_for:
return forwarded_for.split(",")[0].strip()
real_ip = request.headers.get("x-real-ip")
if real_ip:
return real_ip.strip()
# Fallback to direct client address
return request.client.host if request.client else "unknown"