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.
47 lines
2.1 KiB
Python
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()
|