Starting phase 2
This commit is contained in:
parent
65c1954db5
commit
b5d2605d99
224
server/main.py
224
server/main.py
@ -58,23 +58,27 @@ if not public_url.endswith("/"):
|
||||
|
||||
ADMIN_TOKEN = os.getenv("ADMIN_TOKEN", None)
|
||||
|
||||
# Create FastAPI app first
|
||||
app = FastAPI(
|
||||
title="AI Voice Bot Server (Refactored)",
|
||||
description="WebRTC voice chat server with modular architecture",
|
||||
version="2.0.0",
|
||||
)
|
||||
|
||||
logger.info(f"Starting server with public URL: {public_url}")
|
||||
|
||||
|
||||
# Global managers - these replace the global variables from original main.py
|
||||
session_manager: SessionManager = None
|
||||
lobby_manager: LobbyManager = None
|
||||
auth_manager: AuthManager = None
|
||||
websocket_manager: WebSocketConnectionManager = None
|
||||
|
||||
# API routers
|
||||
admin_api: AdminAPI = None
|
||||
session_api: SessionAPI = None
|
||||
lobby_api: LobbyAPI = None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Lifespan context manager for startup and shutdown events"""
|
||||
global session_manager, lobby_manager, auth_manager, websocket_manager
|
||||
global admin_api, session_api, lobby_api
|
||||
|
||||
# Startup
|
||||
logger.info("Starting AI Voice Bot server with modular architecture...")
|
||||
@ -88,14 +92,11 @@ async def lifespan(app: FastAPI):
|
||||
session_manager.load()
|
||||
|
||||
# Restore lobbies for existing sessions
|
||||
# Note: This is a simplified version - full lobby restoration would be more complex
|
||||
for session in session_manager.get_all_sessions():
|
||||
for lobby_info in session.lobbies:
|
||||
# Create lobby if it doesn't exist
|
||||
lobby = lobby_manager.create_or_get_lobby(
|
||||
name=lobby_info.name, private=lobby_info.private
|
||||
)
|
||||
# Add session to lobby (but don't trigger events during startup)
|
||||
with lobby.lock:
|
||||
lobby.sessions[session.id] = session
|
||||
|
||||
@ -109,7 +110,7 @@ async def lifespan(app: FastAPI):
|
||||
auth_manager=auth_manager,
|
||||
)
|
||||
|
||||
# Initialize API routers
|
||||
# Create and register API routes
|
||||
admin_api = AdminAPI(
|
||||
session_manager=session_manager,
|
||||
lobby_manager=lobby_manager,
|
||||
@ -126,11 +127,110 @@ async def lifespan(app: FastAPI):
|
||||
public_url=public_url,
|
||||
)
|
||||
|
||||
# Register API routes
|
||||
# Register API routes during startup
|
||||
app.include_router(admin_api.router)
|
||||
app.include_router(session_api.router)
|
||||
app.include_router(lobby_api.router)
|
||||
|
||||
# Register static file serving AFTER API routes to avoid conflicts
|
||||
PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true"
|
||||
client_build_path = os.path.join(os.path.dirname(__file__), "/client/build")
|
||||
|
||||
if PRODUCTION:
|
||||
logger.info(f"Serving static files from: {client_build_path} at {public_url}")
|
||||
app.mount(
|
||||
public_url,
|
||||
StaticFiles(directory=client_build_path, html=True),
|
||||
name="static",
|
||||
)
|
||||
else:
|
||||
logger.info(f"Proxying static files to http://client:3000 at {public_url}")
|
||||
|
||||
import ssl
|
||||
import httpx
|
||||
|
||||
@app.api_route(
|
||||
f"{public_url}{{path:path}}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"],
|
||||
)
|
||||
async def proxy_static(request: Request, path: str):
|
||||
# Do not proxy API or websocket paths
|
||||
if path.startswith("api/") or path.startswith("ws/"):
|
||||
return Response(status_code=404)
|
||||
url = f"{request.url.scheme}://client:3000/{public_url.strip('/')}/{path}"
|
||||
if not path:
|
||||
url = f"{request.url.scheme}://client:3000/{public_url.strip('/')}"
|
||||
headers = dict(request.headers)
|
||||
try:
|
||||
# Accept self-signed certs in dev
|
||||
async with httpx.AsyncClient(verify=False) as client:
|
||||
proxy_req = client.build_request(
|
||||
request.method,
|
||||
url,
|
||||
headers=headers,
|
||||
content=await request.body(),
|
||||
)
|
||||
proxy_resp = await client.send(proxy_req, stream=True)
|
||||
content = await proxy_resp.aread()
|
||||
|
||||
# Remove problematic headers for browser decoding
|
||||
filtered_headers = {
|
||||
k: v
|
||||
for k, v in proxy_resp.headers.items()
|
||||
if k.lower()
|
||||
not in [
|
||||
"content-encoding",
|
||||
"transfer-encoding",
|
||||
"content-length",
|
||||
]
|
||||
}
|
||||
return Response(
|
||||
content=content,
|
||||
status_code=proxy_resp.status_code,
|
||||
headers=filtered_headers,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Proxy error for {url}: {e}")
|
||||
return Response("Proxy error", status_code=502)
|
||||
|
||||
# WebSocket proxy for /ws (for React DevTools, etc.)
|
||||
import websockets
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_proxy(websocket: WebSocket):
|
||||
logger.info("REACT: WebSocket proxy connection established.")
|
||||
# Get scheme from websocket.url (should be 'ws' or 'wss')
|
||||
scheme = websocket.url.scheme if hasattr(websocket, "url") else "ws"
|
||||
target_url = f"{scheme}://client:3000/ws"
|
||||
await websocket.accept()
|
||||
try:
|
||||
# Accept self-signed certs in dev for WSS
|
||||
ssl_ctx = ssl.create_default_context()
|
||||
ssl_ctx.check_hostname = False
|
||||
ssl_ctx.verify_mode = ssl.CERT_NONE
|
||||
async with websockets.connect(target_url, ssl=ssl_ctx) as target_ws:
|
||||
|
||||
async def client_to_server():
|
||||
while True:
|
||||
msg = await websocket.receive_text()
|
||||
await target_ws.send(msg)
|
||||
|
||||
async def server_to_client():
|
||||
while True:
|
||||
msg = await target_ws.recv()
|
||||
if isinstance(msg, str):
|
||||
await websocket.send_text(msg)
|
||||
else:
|
||||
await websocket.send_bytes(msg)
|
||||
|
||||
try:
|
||||
await asyncio.gather(client_to_server(), server_to_client())
|
||||
except (WebSocketDisconnect, websockets.ConnectionClosed):
|
||||
logger.info("REACT: WebSocket proxy connection closed.")
|
||||
except Exception as e:
|
||||
logger.error(f"REACT: WebSocket proxy error: {e}")
|
||||
await websocket.close()
|
||||
|
||||
# Start background tasks
|
||||
await session_manager.start_background_tasks()
|
||||
|
||||
@ -157,15 +257,8 @@ async def lifespan(app: FastAPI):
|
||||
logger.info("Server shutdown complete")
|
||||
|
||||
|
||||
# Create FastAPI app
|
||||
app = FastAPI(
|
||||
title="AI Voice Bot Server (Refactored)",
|
||||
description="WebRTC voice chat server with modular architecture",
|
||||
version="2.0.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
logger.info(f"Starting server with public URL: {public_url}")
|
||||
# Set the lifespan
|
||||
app.router.lifespan_context = lifespan
|
||||
|
||||
|
||||
@app.websocket(f"{public_url}" + "ws/lobby/{lobby_id}/{session_id}")
|
||||
@ -202,97 +295,6 @@ def system_health():
|
||||
}
|
||||
|
||||
|
||||
# Serve static files or proxy to frontend development server
|
||||
PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true"
|
||||
client_build_path = os.path.join(os.path.dirname(__file__), "/client/build")
|
||||
|
||||
if PRODUCTION:
|
||||
logger.info(f"Serving static files from: {client_build_path} at {public_url}")
|
||||
app.mount(
|
||||
public_url, StaticFiles(directory=client_build_path, html=True), name="static"
|
||||
)
|
||||
else:
|
||||
logger.info(f"Proxying static files to http://client:3000 at {public_url}")
|
||||
|
||||
import ssl
|
||||
import httpx
|
||||
|
||||
@app.api_route(
|
||||
f"{public_url}{{path:path}}",
|
||||
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"],
|
||||
)
|
||||
async def proxy_static(request: Request, path: str):
|
||||
# Do not proxy API or websocket paths
|
||||
if path.startswith("api/") or path.startswith("ws/"):
|
||||
return Response(status_code=404)
|
||||
url = f"{request.url.scheme}://client:3000/{public_url.strip('/')}/{path}"
|
||||
if not path:
|
||||
url = f"{request.url.scheme}://client:3000/{public_url.strip('/')}"
|
||||
headers = dict(request.headers)
|
||||
try:
|
||||
# Accept self-signed certs in dev
|
||||
async with httpx.AsyncClient(verify=False) as client:
|
||||
proxy_req = client.build_request(
|
||||
request.method, url, headers=headers, content=await request.body()
|
||||
)
|
||||
proxy_resp = await client.send(proxy_req, stream=True)
|
||||
content = await proxy_resp.aread()
|
||||
|
||||
# Remove problematic headers for browser decoding
|
||||
filtered_headers = {
|
||||
k: v
|
||||
for k, v in proxy_resp.headers.items()
|
||||
if k.lower()
|
||||
not in ["content-encoding", "transfer-encoding", "content-length"]
|
||||
}
|
||||
return Response(
|
||||
content=content,
|
||||
status_code=proxy_resp.status_code,
|
||||
headers=filtered_headers,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Proxy error for {url}: {e}")
|
||||
return Response("Proxy error", status_code=502)
|
||||
|
||||
# WebSocket proxy for /ws (for React DevTools, etc.)
|
||||
import websockets
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_proxy(websocket: WebSocket):
|
||||
logger.info("REACT: WebSocket proxy connection established.")
|
||||
# Get scheme from websocket.url (should be 'ws' or 'wss')
|
||||
scheme = websocket.url.scheme if hasattr(websocket, "url") else "ws"
|
||||
target_url = f"{scheme}://client:3000/ws"
|
||||
await websocket.accept()
|
||||
try:
|
||||
# Accept self-signed certs in dev for WSS
|
||||
ssl_ctx = ssl.create_default_context()
|
||||
ssl_ctx.check_hostname = False
|
||||
ssl_ctx.verify_mode = ssl.CERT_NONE
|
||||
async with websockets.connect(target_url, ssl=ssl_ctx) as target_ws:
|
||||
|
||||
async def client_to_server():
|
||||
while True:
|
||||
msg = await websocket.receive_text()
|
||||
await target_ws.send(msg)
|
||||
|
||||
async def server_to_client():
|
||||
while True:
|
||||
msg = await target_ws.recv()
|
||||
if isinstance(msg, str):
|
||||
await websocket.send_text(msg)
|
||||
else:
|
||||
await websocket.send_bytes(msg)
|
||||
|
||||
try:
|
||||
await asyncio.gather(client_to_server(), server_to_client())
|
||||
except (WebSocketDisconnect, websockets.ConnectionClosed):
|
||||
logger.info("REACT: WebSocket proxy connection closed.")
|
||||
except Exception as e:
|
||||
logger.error(f"REACT: WebSocket proxy error: {e}")
|
||||
await websocket.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user