"""Reverse-proxy everything that is not API to the Next.js dashboard (127.0.0.1:8301)."""
from __future__ import annotations
import httpx
from fastapi import APIRouter, Request, Response
from fastapi.responses import HTMLResponse, StreamingResponse
router = APIRouter()
# content-encoding is deliberately kept: the body is forwarded as-is (compressed) so the header must stay with it.
HOP_BY_HOP = {"connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers",
"transfer-encoding", "upgrade", "content-length"}
FALLBACK_HTML = """
LLM API
LLM API
The API is running but the dashboard is not reachable yet.
OpenAI-compatible endpoint: /v1/chat/completions ยท health: /health
"""
@router.api_route("/{path:path}", methods=["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], include_in_schema=False)
async def proxy(path: str, request: Request):
settings = request.app.state.settings
client: httpx.AsyncClient = request.app.state.ui_client
url = f"{settings.web_url.rstrip('/')}/{path}"
if request.url.query:
url += f"?{request.url.query}"
headers = {k: v for k, v in request.headers.items() if k.lower() not in HOP_BY_HOP and k.lower() != "host"}
headers["x-forwarded-host"] = request.headers.get("host", "")
headers["x-forwarded-proto"] = request.headers.get("x-forwarded-proto", request.url.scheme)
body = await request.body()
try:
req = client.build_request(request.method, url, headers=headers, content=body)
resp = await client.send(req, stream=True)
except httpx.HTTPError:
return HTMLResponse(FALLBACK_HTML, status_code=503)
out_headers = {k: v for k, v in resp.headers.items() if k.lower() not in HOP_BY_HOP}
async def body_iter():
try:
async for chunk in resp.aiter_raw():
yield chunk
finally:
await resp.aclose()
if request.method == "HEAD":
await resp.aclose()
return Response(status_code=resp.status_code, headers=out_headers)
return StreamingResponse(body_iter(), status_code=resp.status_code, headers=out_headers,
media_type=resp.headers.get("content-type"))