iscc-app / sinetti_api /upload_limits.py
Couscouskabyle's picture
Deploy Sinetti V2 (FastAPI + React) [publication:de66c457b10849fc9d3e7672a9138ceb]
b196778 verified
Raw History Blame
2.54 kB
"""Early, bounded ingress for the batch-check multipart upload."""
from __future__ import annotations
from starlette.types import ASGIApp, Message, Receive, Scope, Send
_TOO_LARGE = b'{"ok":false,"message":"That file is too large to check."}'
# The route still enforces an exact 8 MiB file. This separate ingress allowance leaves
# bounded room for multipart field names, headers, boundary markers, and the final delimiter.
DEFAULT_MAX_BODY_BYTES = 8_000_000 + 64 * 1024
class _UploadTooLarge(Exception):
pass
class BatchCheckUploadLimitMiddleware:
"""Stop an oversized batch-check body before Starlette spools multipart parts."""
def __init__(self, app: ASGIApp, *, max_bytes: int = DEFAULT_MAX_BODY_BYTES) -> None:
self.app, self.max_bytes = app, max_bytes
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http" or scope.get("method") != "POST" or scope.get("path") != "/api/batch-check":
await self.app(scope, receive, send)
return
declared = dict(scope.get("headers") or ()).get(b"content-length", b"")
if declared.isdigit() and int(declared) > self.max_bytes:
await self._send_refusal(send)
return
total = 0
overflow = False
async def limited_receive() -> Message:
nonlocal total, overflow
event = await receive()
if event["type"] == "http.request":
total += len(event.get("body", b""))
if total > self.max_bytes:
overflow = True
raise _UploadTooLarge
return event
async def limited_send(event: Message) -> None:
# Starlette turns the receive exception into a multipart 400. Once overflow is
# known, discard that response and preserve the route's existing JSON contract.
if not overflow:
await send(event)
try:
await self.app(scope, limited_receive, limited_send)
except _UploadTooLarge:
overflow = True
if overflow:
await self._send_refusal(send)
@staticmethod
async def _send_refusal(send: Send) -> None:
await send({"type": "http.response.start", "status": 200, "headers": [(b"content-type", b"application/json"), (b"content-length", str(len(_TOO_LARGE)).encode())]})
await send({"type": "http.response.body", "body": _TOO_LARGE})