import httpx from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware import uvicorn import subprocess import signal app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) PYTHON_PORT = 7860 NODE_PORT = 4321 NODE_SCRIPT_PATH = "build" node_process = subprocess.Popen(["node", NODE_SCRIPT_PATH]) def handle_sigterm(signum, frame): print("Stopping Node.js server...") node_process.terminate() node_process.wait() exit(0) signal.signal(signal.SIGTERM, handle_sigterm) @app.on_event("shutdown") def shutdown_event(): print("Stopping Node.js server...") node_process.terminate() node_process.wait() @app.get("/config") async def route_with_config(): return JSONResponse(content={"one": "hello", "two": "from", "three": "Python"}) async def proxy_to_node(request: Request): client = httpx.AsyncClient() # Preserve the full path including query parameters full_path = request.url.path if request.url.query: full_path += f"?{request.url.query}" url = f"http://localhost:{NODE_PORT}{full_path}" headers = { k: v for k, v in request.headers.items() if k.lower() not in ["host", "content-length"] } body = await request.body() async with client: response = await client.request( method=request.method, url=url, headers=headers, content=body ) return StreamingResponse( response.iter_bytes(), status_code=response.status_code, headers=response.headers, ) @app.api_route( "/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] ) async def catch_all(request: Request, path: str): return await proxy_to_node(request) if __name__ == "__main__": print( f"Starting dual server. Python handles specific routes, Node handles the rest." ) uvicorn.run(app, host="0.0.0.0", port=PYTHON_PORT)