Spaces:
Running
Running
| import time | |
| from typing import Dict, Tuple | |
| from starlette.requests import Request # type: ignore | |
| from starlette.responses import Response # type: ignore | |
| from starlette.types import ASGIApp, Receive, Scope, Send # type: ignore | |
| class SimpleRateLimiter: | |
| """Naive in-memory rate limiter (per IP). Suitable for single-instance deployments.""" | |
| def __init__(self, app: ASGIApp, max_requests: int = 100, window_seconds: int = 60): | |
| self.app = app | |
| self.max_requests = max_requests | |
| self.window_seconds = window_seconds | |
| self.ip_buckets: Dict[str, Tuple[int, float]] = {} | |
| async def __call__(self, scope: Scope, receive: Receive, send: Send): | |
| if scope["type"] != "http": | |
| await self.app(scope, receive, send) | |
| return | |
| request = Request(scope, receive=receive) | |
| client_ip = request.client.host if request.client else "unknown" | |
| now = time.time() | |
| count, start = self.ip_buckets.get(client_ip, (0, now)) | |
| if now - start > self.window_seconds: | |
| count, start = 0, now | |
| count += 1 | |
| self.ip_buckets[client_ip] = (count, start) | |
| if count > self.max_requests: | |
| response = Response("Rate limit exceeded", status_code=429) | |
| await response(scope, receive, send) | |
| return | |
| await self.app(scope, receive, send) | |
| class MaxUploadSizeLimiter: | |
| """Limit maximum request body size for uploads.""" | |
| def __init__(self, app: ASGIApp, max_bytes: int = 10 * 1024 * 1024): | |
| self.app = app | |
| self.max_bytes = max_bytes | |
| async def __call__(self, scope: Scope, receive: Receive, send: Send): | |
| if scope["type"] != "http": | |
| await self.app(scope, receive, send) | |
| return | |
| received = 0 | |
| async def limited_receive(): | |
| nonlocal received | |
| message = await receive() | |
| if message.get("type") == "http.request": | |
| body = message.get("body", b"") | |
| received += len(body) | |
| if received > self.max_bytes: | |
| response = Response("Payload too large", status_code=413) | |
| await response(scope, receive, send) | |
| return {"type": "http.disconnect"} | |
| return message | |
| await self.app(scope, limited_receive, send) | |