Files
mission-control-v2/backend/routers/gateway_forward.py
Hitonabi 47f7a85510 Ruff-Cleanup: ganzes MC2-Repo lint-grün + projekt-passende ruff.toml
Die Ampel-Nachruestung deckte 317/337 vorbestehende ruff-Verstoesse im ganzen Repo
auf. Aufgeraeumt:
- ruff.toml: intentionale Muster als Projekt-Politik ausgenommen (BLE001 blind-except,
  S110/S112 try-except-pass/continue, PLW1510 subprocess-best-effort, B008 FastAPI-
  Depends/File-Idiom, EXE001 Shebang, + wenige Stil-Regeln). __init__.py-Re-Exports
  geschuetzt (F401).
- ruff --fix: 128 mechanische (Import-Sortierung, PEP585/604-Annotationen, tote Imports,
  ueberfluessige noqa) auto-behoben.
- 12 echte Reste von Hand: PERF402/102, PLC3002 (Lambda->walrus), ISC004 (String-Concat
  geklammert), F841/RUF059 (ungenutzte Vars), PIE810 (startswith-Tuple), UP031 (f-string),
  UP035 (veraltete typing-Imports).
Ergebnis: 'ruff check .' = 0, 'compileall' grün. Kein Verhaltenswechsel (nur Stil/Modernisierung).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 20:53:36 +02:00

67 lines
2.5 KiB
Python

"""
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)
)