File size: 2,192 Bytes
12fd5f2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | """
API middleware for request logging, rate limiting, and error handling.
"""
from fastapi import Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from loguru import logger
import time
from collections import defaultdict, deque
class RequestLoggingMiddleware(BaseHTTPMiddleware):
"""Logs all incoming requests with timing information."""
async def dispatch(self, request: Request, call_next):
start_time = time.time()
path = request.url.path
method = request.method
logger.info(f"→ {method} {path}")
try:
response = await call_next(request)
except Exception as e:
logger.error(f"✗ {method} {path} - Error: {e}")
raise
elapsed = (time.time() - start_time) * 1000 # ms
logger.info(f"← {method} {path} - {response.status_code} ({elapsed:.1f}ms)")
return response
class RateLimitMiddleware(BaseHTTPMiddleware):
"""Simple in-memory rate limiting."""
def __init__(self, app, max_requests_per_minute: int = 60):
super().__init__(app)
self.max_requests = max_requests_per_minute
self.window = 60 # seconds
# Track requests per client IP: {ip: deque([timestamp, ...])}
self.requests: dict = defaultdict(deque)
async def dispatch(self, request: Request, call_next):
# Get client IP
client_ip = request.client.host if request.client else "unknown"
now = time.time()
# Clean old entries
timestamps = self.requests[client_ip]
while timestamps and timestamps[0] < now - self.window:
timestamps.popleft()
# Check rate limit
if len(timestamps) >= self.max_requests:
logger.warning(f"Rate limited: {client_ip} ({len(timestamps)} requests in {self.window}s)")
return JSONResponse(
status_code=429,
content={"detail": "Rate limit exceeded. Please wait before making more requests."},
)
# Record this request
timestamps.append(now)
response = await call_next(request)
return response
|