47f7a85510
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>
67 lines
2.5 KiB
Python
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)
|
|
)
|