confluence_web/backend/upstream.py
Artur Mukhamadiev e65fbf4b67 backend: FastAPI backend track handoff (contract revision 1)
FastAPI app, upstream Confluence/model adapters, authoritative history,
rootless container lifecycle, artifact storage and downloads, fake peers
under backend/dev, tests under tests/backend. Root pytest.ini deselects
the live marker by default; requirements gain the backend dependencies.
2026-09-14 21:57:54 +03:00

47 lines
2.1 KiB
Python

"""Bound wire and decoded response bytes before JSON parsing."""
import asyncio
import json
import zlib
from backend.errors import UpstreamFailedError, UpstreamResponseTooLargeError
async def read_json(response, limit):
try:
length = response.headers.get("content-length")
if length is not None and int(length) > limit:
raise UpstreamResponseTooLargeError()
if response.is_stream_consumed:
body = response.content
if len(body) > limit:
raise UpstreamResponseTooLargeError()
else:
encoding = response.headers.get("content-encoding", "identity").lower()
if encoding not in ("identity", "gzip", "deflate"):
raise UpstreamFailedError("Unsupported upstream content encoding")
decoder = zlib.decompressobj(31 if encoding == "gzip" else 15) if encoding != "identity" else None
body = bytearray()
wire_bytes = 0
async for chunk in response.aiter_raw(chunk_size=64 * 1024):
wire_bytes += len(chunk)
if wire_bytes > limit:
raise UpstreamResponseTooLargeError()
decoded = decoder.decompress(chunk, limit - len(body) + 1) if decoder else chunk
if len(body) + len(decoded) > limit or (decoder and decoder.unconsumed_tail):
raise UpstreamResponseTooLargeError()
body.extend(decoded)
if decoder:
tail = decoder.flush(limit - len(body) + 1)
if len(body) + len(tail) > limit:
raise UpstreamResponseTooLargeError()
body.extend(tail)
if not decoder.eof or decoder.unused_data:
raise UpstreamFailedError("Invalid upstream compressed response")
return await asyncio.to_thread(json.loads, body)
except (UpstreamFailedError, UpstreamResponseTooLargeError):
raise
except Exception as exc:
raise UpstreamFailedError("Invalid upstream JSON response") from exc
finally:
await response.aclose()