SPB Git forge

spb/llm-api

Public
0commits 0branches 0releases
0 Bsize
maindefault branch
—last push
2.6 KB · 52 lines python
Raw Blame History
1"""Reverse-proxy everything that is not API to the Next.js dashboard (127.0.0.1:8301)."""23from __future__ import annotations45import httpx6from fastapi import APIRouter, Request, Response7from fastapi.responses import HTMLResponse, StreamingResponse89router = APIRouter()1011# content-encoding is deliberately kept: the body is forwarded as-is (compressed) so the header must stay with it.12HOP_BY_HOP = {"connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers",13              "transfer-encoding", "upgrade", "content-length"}1415FALLBACK_HTML = """<!doctype html><html><head><meta charset="utf-8"><title>LLM API</title>16<style>body{font-family:-apple-system,system-ui,sans-serif;background:#0b0d10;color:#e6e8eb;display:grid;place-items:center;height:100vh;margin:0}17main{max-width:520px;padding:32px;border:1px solid #23272d;border-radius:14px;background:#11141a}code{color:#8fd3ff}</style></head>18<body><main><h1>LLM API</h1><p>The API is running but the dashboard is not reachable yet.</p>19<p>OpenAI-compatible endpoint: <code>/v1/chat/completions</code> · health: <code>/health</code></p></main></body></html>"""202122@router.api_route("/{path:path}", methods=["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], include_in_schema=False)23async def proxy(path: str, request: Request):24    settings = request.app.state.settings25    client: httpx.AsyncClient = request.app.state.ui_client26    url = f"{settings.web_url.rstrip('/')}/{path}"27    if request.url.query:28        url += f"?{request.url.query}"29    headers = {k: v for k, v in request.headers.items() if k.lower() not in HOP_BY_HOP and k.lower() != "host"}30    headers["x-forwarded-host"] = request.headers.get("host", "")31    headers["x-forwarded-proto"] = request.headers.get("x-forwarded-proto", request.url.scheme)32    body = await request.body()33    try:34        req = client.build_request(request.method, url, headers=headers, content=body)35        resp = await client.send(req, stream=True)36    except httpx.HTTPError:37        return HTMLResponse(FALLBACK_HTML, status_code=503)38    out_headers = {k: v for k, v in resp.headers.items() if k.lower() not in HOP_BY_HOP}3940    async def body_iter():41        try:42            async for chunk in resp.aiter_raw():43                yield chunk44        finally:45            await resp.aclose()4647    if request.method == "HEAD":48        await resp.aclose()49        return Response(status_code=resp.status_code, headers=out_headers)50    return StreamingResponse(body_iter(), status_code=resp.status_code, headers=out_headers,51                             media_type=resp.headers.get("content-type"))52