""" Dünner /v1-Roh-Weiterleiter (UMBAU v3, P1). Wenn MC_V1_UPSTREAM gesetzt ist (Unit-Env), reicht das Steuerpult /v1 unangefasst an den eigenständigen mc2-gateway-Prozess (Loopback :9010) durch — LAN-Clients wie die IDE-Lane erreichen den Gateway sonst nicht. Hier passiert BEWUSST nichts: kein Routing, keine Bild-Weiche, keine Token-Zählung — all das macht genau einmal der Gateway-Prozess (routers/gateway_proxy.py). Rollback = Env-Zeile aus der Unit entfernen → app.py bindet wieder den lokalen Gateway ein. """ import logging from config import V1_UPSTREAM from fastapi import APIRouter, Request from fastapi.responses import JSONResponse, StreamingResponse log = logging.getLogger(__name__) router = APIRouter(prefix="/v1") # Hop-by-hop-Header dürfen nicht blind weitergereicht werden; content-length wird von # httpx (Request) bzw. Starlette-Chunking (Response) neu bestimmt. _HOP_HEADERS = { "host", "content-length", "connection", "keep-alive", "transfer-encoding", "upgrade", "proxy-authenticate", "proxy-authorization", "te", "trailer", } def _clean(headers) -> dict: return {k: v for k, v in headers.items() if k.lower() not in _HOP_HEADERS} @router.api_route("/{path:path}", methods=["GET", "POST"]) async def forward(path: str, request: Request): client = request.app.state.gw_client # geteilter Keep-Alive-Client (app.py lifespan) url = f"{V1_UPSTREAM}/v1/{path}" if request.url.query: url = f"{url}?{request.url.query}" body = await request.body() req = client.build_request( request.method, url, content=body or None, headers=_clean(request.headers), timeout=None, ) try: upstream = await client.send(req, stream=True) except Exception as exc: # Gateway-Prozess weg → ehrlicher 502 statt Hänger log.warning("/v1-Weiterleitung an %s fehlgeschlagen: %s", V1_UPSTREAM, exc) return JSONResponse( {"error": {"message": f"mc2-gateway ({V1_UPSTREAM}) nicht erreichbar: {exc}", "type": "gateway_unavailable"}}, status_code=502, ) async def gen(): try: async for chunk in upstream.aiter_raw(): yield chunk finally: await upstream.aclose() # Streaming-Passthrough für BEIDE Fälle (SSE + normale JSON-Antwort): Starlette # chunkt selbst, Status/Header (inkl. x-mc-routed-to) kommen vom Gateway. return StreamingResponse( gen(), status_code=upstream.status_code, headers=_clean(upstream.headers) )