feat(ws): implement full duplex bi-directional WebSocket proxy bridge supporting subdomains and path-based routing
This commit is contained in:
+34
-3
@@ -1,4 +1,4 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
@@ -8,7 +8,7 @@ from contextlib import asynccontextmanager
|
||||
|
||||
from app.config import settings, UNIVERSES, BASE_DIR
|
||||
from app.routers import api, proxy
|
||||
from app.proxy import close_http_client, proxy_request
|
||||
from app.proxy import close_http_client, proxy_request, proxy_websocket
|
||||
from app.personas import ensure_dirs
|
||||
|
||||
def get_subdomain_target(host: str) -> Optional[Tuple[str, str]]:
|
||||
@@ -55,7 +55,7 @@ app = FastAPI(
|
||||
lifespan=lifespan
|
||||
)
|
||||
|
||||
# Subdomain Routing Middleware
|
||||
# Subdomain Routing Middleware for HTTP
|
||||
@app.middleware("http")
|
||||
async def subdomain_routing_middleware(request: Request, call_next):
|
||||
host = request.headers.get("host", "")
|
||||
@@ -96,6 +96,37 @@ async def index_view(request: Request):
|
||||
}
|
||||
)
|
||||
|
||||
# WebSocket Proxy Route (intercepts any WebSocket connection across subdomains)
|
||||
@app.websocket("/{path:path}")
|
||||
@app.websocket("")
|
||||
async def websocket_proxy_endpoint(websocket: WebSocket, path: str = ""):
|
||||
host = websocket.headers.get("host", "")
|
||||
target = get_subdomain_target(host)
|
||||
|
||||
if target:
|
||||
backend_url, universe_id = target
|
||||
await proxy_websocket(
|
||||
client_ws=websocket,
|
||||
backend_url=backend_url,
|
||||
path=path,
|
||||
universe_id=universe_id
|
||||
)
|
||||
elif path.startswith("u/"):
|
||||
parts = path.split("/", 2)
|
||||
if len(parts) >= 2 and parts[1] in UNIVERSES:
|
||||
u_id = parts[1]
|
||||
subpath = parts[2] if len(parts) > 2 else ""
|
||||
await proxy_websocket(
|
||||
client_ws=websocket,
|
||||
backend_url=UNIVERSES[u_id].backend_url,
|
||||
path=subpath,
|
||||
universe_id=u_id
|
||||
)
|
||||
else:
|
||||
await websocket.close(code=1008)
|
||||
else:
|
||||
await websocket.close(code=1008)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run("app.main:app", host=settings.host, port=settings.port, reload=settings.debug)
|
||||
|
||||
Reference in New Issue
Block a user