| import asyncio |
| import os |
| import signal |
| import subprocess |
| import sys |
| from contextlib import asynccontextmanager |
|
|
| import httpx |
| import uvicorn |
| from fastapi import FastAPI, Request |
| from fastapi.responses import Response, StreamingResponse |
|
|
| MANAGER_URL = "http://127.0.0.1:7894" |
| API_URL = "http://127.0.0.1:7895" |
|
|
| processes = [] |
|
|
|
|
| def start_process(command): |
| env = os.environ.copy() |
| env.setdefault("DB_PATH", "/data/privy_manager.db") |
| return subprocess.Popen(command, env=env) |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| processes.append(start_process([sys.executable, "src/privy_manager.py"])) |
| processes.append(start_process([sys.executable, "src/api_server.py"])) |
| await asyncio.sleep(3) |
| try: |
| yield |
| finally: |
| for proc in processes: |
| if proc.poll() is None: |
| proc.send_signal(signal.SIGTERM) |
| for proc in processes: |
| try: |
| proc.wait(timeout=5) |
| except subprocess.TimeoutExpired: |
| proc.kill() |
|
|
|
|
| app = FastAPI(lifespan=lifespan) |
|
|
|
|
| async def proxy(request: Request, upstream: str, path: str): |
| url = f"{upstream}/{path}" if path else upstream |
| if request.url.query: |
| url = f"{url}?{request.url.query}" |
|
|
| headers = dict(request.headers) |
| headers.pop("host", None) |
| body = await request.body() |
|
|
| client = httpx.AsyncClient(timeout=None, follow_redirects=False) |
| req = client.build_request( |
| request.method, |
| url, |
| headers=headers, |
| content=body, |
| ) |
| upstream_resp = await client.send(req, stream=True) |
|
|
| excluded = {"content-encoding", "transfer-encoding", "connection"} |
| response_headers = {k: v for k, v in upstream_resp.headers.items() if k.lower() not in excluded} |
|
|
| async def stream_response(): |
| try: |
| async for chunk in upstream_resp.aiter_raw(): |
| yield chunk |
| finally: |
| await upstream_resp.aclose() |
| await client.aclose() |
|
|
| return StreamingResponse( |
| stream_response(), |
| status_code=upstream_resp.status_code, |
| headers=response_headers, |
| media_type=upstream_resp.headers.get("content-type"), |
| ) |
|
|
|
|
| @app.api_route("/v1/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]) |
| async def api_proxy(request: Request, path: str): |
| return await proxy(request, API_URL, f"v1/{path}") |
|
|
|
|
| @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]) |
| async def manager_proxy(request: Request, path: str): |
| return await proxy(request, MANAGER_URL, path) |
|
|
|
|
| if __name__ == "__main__": |
| port = int(os.environ.get("PORT", "7860")) |
| uvicorn.run(app, host="0.0.0.0", port=port) |
|
|