Spaces:
Running
Running
File size: 4,604 Bytes
be9292f 774321b 7e8f934 774321b b82ec56 ec062c4 774321b fdcf5d8 9ee287c 318fb2d 9ee287c ec062c4 b93d024 774321b fdcf5d8 ec062c4 d8e2f72 774321b d8e2f72 fdcf5d8 774321b 9ee287c 774321b 9ee287c 774321b fdcf5d8 774321b fdcf5d8 774321b fdcf5d8 774321b ec062c4 7e8f934 ec062c4 774321b b82ec56 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | import os
import httpx
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
from starlette.background import BackgroundTask
from starlette.responses import StreamingResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
app = FastAPI()
# --- Configuration ---
# The URL of your PRIVATE calculation server.
PRIVATE_SERVER_URL = os.environ.get("PRIVATE_SERVER_URL")
# The broker will read your HF_TOKEN from the Space's secrets.
HF_TOKEN = os.environ.get("HF_TOKEN")
GOOGLE_MAPS_API_KEY = os.environ.get("VITE_GOOGLE_MAPS_API_KEY")
if not PRIVATE_SERVER_URL:
print("WARNING: The `PRIVATE_SERVER_URL` environment variable is not set. The broker will not be able to forward requests.")
if not HF_TOKEN:
print("WARNING: The `HF_TOKEN` environment variable is not set. Requests to the private server will not be authenticated.")
if not GOOGLE_MAPS_API_KEY:
print("WARNING: The `VITE_GOOGLE_MAPS_API_KEY` environment variable is not set. The map on the visit log page will not load.")
# Read timeout from env var, default to 5 minutes (300 seconds) to accommodate slow model conversions.
BROKER_TIMEOUT = float(os.environ.get("BROKER_TIMEOUT", 300.0))
# Use a persistent client for performance (connection pooling).
client = httpx.AsyncClient(base_url=PRIVATE_SERVER_URL, timeout=BROKER_TIMEOUT)
async def _reverse_proxy(request: Request):
"""
This function acts as a reverse proxy. It captures incoming requests,
forwards them to the private backend server, and streams the response back.
"""
print(f"Broker: Received request: {request.method} {request.url.path}")
if HF_TOKEN:
token_preview = HF_TOKEN[:4] + "..." + HF_TOKEN[-4:]
print(f"Broker: Using HF_TOKEN: {token_preview}")
else:
print("Broker: WARNING - HF_TOKEN is not set. Request will be unauthenticated.")
print(f"Broker: Target private server URL: {PRIVATE_SERVER_URL}")
if not PRIVATE_SERVER_URL:
return Response(status_code=503, content="Service Unavailable: Broker is not configured. `PRIVATE_SERVER_URL` is missing.")
forward_headers = httpx.Headers(request.headers)
forward_headers.pop("host", None)
if HF_TOKEN:
forward_headers["Authorization"] = f"Bearer {HF_TOKEN}"
url = httpx.URL(path=request.url.path, query=request.url.query.encode("utf-8"))
backend_request = client.build_request(method=request.method, url=url, headers=forward_headers, content=request.stream())
print(f"Broker: Forwarding request to: {backend_request.method} {backend_request.url}")
try:
backend_response = await client.send(backend_request, stream=True)
print(f"Broker: Received response from backend with status: {backend_response.status_code}")
return StreamingResponse(backend_response.aiter_raw(), status_code=backend_response.status_code, headers=backend_response.headers, background=BackgroundTask(backend_response.aclose))
except httpx.ConnectError as e:
error_message = f"Connection to private server failed: {e}"
print(f"Broker: CRITICAL - {error_message}")
return Response(status_code=502, content=f"Bad Gateway: The broker could not connect to the private server. Details: {error_message}")
# --- Client Configuration Endpoint ---
# This endpoint must be defined *before* the reverse proxy routes.
class ClientConfig(BaseModel):
googleMapsApiKey: Optional[str]
@app.get("/api/config", response_model=ClientConfig)
async def get_client_config():
"""Provides public-safe configuration variables to the client at runtime."""
return ClientConfig(googleMapsApiKey=GOOGLE_MAPS_API_KEY)
# Add routes to capture all API and data requests and forward them.
app.add_route("/api/{path:path}", _reverse_proxy, ["GET", "POST", "PUT", "DELETE"])
app.add_route("/data/{path:path}", _reverse_proxy, ["GET"])
# Enable CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# --- Static File Serving for Production ---
# This must come *after* the API routes to avoid conflicts.
# This mounts the 'static' directory (containing the built React app) at the root.
# The `html=True` argument enables SPA mode, serving `index.html` for any path
# that doesn't match a file. This is the recommended and most robust way to
# serve a Single-Page Application with client-side routing.
app.mount("/", StaticFiles(directory="static", html=True), name="static_root")
|