Spaces:
Running
Running
| """Read-only public gate for the live-demo engine surface. | |
| The capture/write socket stays private on Tailnet. This app is the origin behind the public | |
| Cloudflare Tunnel hostname and only proxies viewer/library reads through to the local engine. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from collections.abc import AsyncIterator | |
| from contextlib import asynccontextmanager | |
| import httpx | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import PlainTextResponse, Response, StreamingResponse | |
| ORIGIN_ENV = "SMALL_CUTS_ORIGIN_ENGINE_URL" | |
| DEFAULT_ORIGIN = "http://127.0.0.1:8077" | |
| READ_TIMEOUT = httpx.Timeout(connect=5.0, read=30.0, write=5.0, pool=5.0) | |
| STREAM_TIMEOUT = httpx.Timeout(connect=5.0, read=None, write=5.0, pool=5.0) | |
| BLOCKED_TEXT = "small-cuts public endpoint is read-only\n" | |
| HOP_BY_HOP_HEADERS = { | |
| "connection", | |
| "keep-alive", | |
| "proxy-authenticate", | |
| "proxy-authorization", | |
| "te", | |
| "trailer", | |
| "transfer-encoding", | |
| "upgrade", | |
| } | |
| def is_public_read_allowed(method: str, path: str) -> bool: | |
| if method.upper() != "GET": | |
| return False | |
| return path in ("/v1/scenes", "/v1/scenes/stream") or path.startswith("/media/") | |
| def _forward_headers(headers: httpx.Headers | dict[str, str]) -> dict[str, str]: | |
| return { | |
| key: value | |
| for key, value in headers.items() | |
| if key.lower() not in HOP_BY_HOP_HEADERS and key.lower() != "host" | |
| } | |
| def _origin_url(origin_url: str, request: Request) -> str: | |
| url = f"{origin_url.rstrip('/')}{request.url.path}" | |
| return f"{url}?{request.url.query}" if request.url.query else url | |
| def _timeout_for_path(path: str) -> httpx.Timeout: | |
| return STREAM_TIMEOUT if path == "/v1/scenes/stream" else READ_TIMEOUT | |
| async def _proxy_body(upstream: httpx.Response) -> AsyncIterator[bytes]: | |
| try: | |
| async for chunk in upstream.aiter_raw(): | |
| yield chunk | |
| finally: | |
| await upstream.aclose() | |
| def build_read_gate_app(origin_url: str | None = None) -> FastAPI: | |
| origin = (origin_url or os.environ.get(ORIGIN_ENV) or DEFAULT_ORIGIN).rstrip("/") | |
| async def lifespan(app: FastAPI) -> AsyncIterator[None]: | |
| app.state.client = httpx.AsyncClient() | |
| try: | |
| yield | |
| finally: | |
| await app.state.client.aclose() | |
| app = FastAPI(title="small-cuts public read gate", lifespan=lifespan) | |
| async def public_gate(path: str, request: Request) -> Response: | |
| if not is_public_read_allowed(request.method, request.url.path): | |
| return PlainTextResponse(BLOCKED_TEXT, status_code=403) | |
| client: httpx.AsyncClient = request.app.state.client | |
| upstream = await client.send( | |
| client.build_request( | |
| "GET", | |
| _origin_url(origin, request), | |
| headers=_forward_headers(request.headers), | |
| timeout=_timeout_for_path(request.url.path), | |
| ), | |
| stream=True, | |
| ) | |
| return StreamingResponse( | |
| _proxy_body(upstream), | |
| status_code=upstream.status_code, | |
| headers=_forward_headers(upstream.headers), | |
| media_type=upstream.headers.get("content-type"), | |
| ) | |
| return app | |
| app = build_read_gate_app() | |