"""Simple static file server with API and WebSocket proxy""" import asyncio import http.server import websockets import threading import urllib.request import urllib.error import os import json PORT = 8006 DIST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dist") API_BASE = "http://127.0.0.1:8004" WS_BASE = "ws://127.0.0.1:8004" class KVMHandler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory=DIST_DIR, **kwargs) def do_GET(self): if self.path.startswith("/api/"): self._proxy("GET") elif self.path == "/" or self.path == "": self.path = "/index.html" super().do_GET() else: file_path = os.path.join(DIST_DIR, self.path.lstrip("/")) if os.path.isfile(file_path): super().do_GET() else: self.path = "/index.html" super().do_GET() def do_POST(self): if self.path.startswith("/api/"): self._proxy("POST") else: self.send_error(404) def do_PUT(self): if self.path.startswith("/api/"): self._proxy("PUT") else: self.send_error(404) def do_DELETE(self): if self.path.startswith("/api/"): self._proxy("DELETE") else: self.send_error(404) def _proxy(self, method): content_length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(content_length) if content_length > 0 else None url = f"{API_BASE}{self.path}" req = urllib.request.Request(url, data=body, method=method) for key in ["Content-Type", "Authorization"]: if key in self.headers: req.add_header(key, self.headers[key]) try: with urllib.request.urlopen(req, timeout=30) as resp: resp_body = resp.read() self.send_response(resp.status) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(resp_body) except urllib.error.HTTPError as e: resp_body = e.read() self.send_response(e.code) self.send_header("Content-Type", "application/json") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(resp_body) except Exception as e: self.send_response(502) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"error": str(e)}).encode()) def log_message(self, format, *args): pass async def websocket_proxy(websocket, path): """Proxy WebSocket connections to backend""" ws_url = f"{WS_BASE}{path}" try: async with websockets.connect(ws_url) as backend_ws: async def forward_to_backend(): async for msg in websocket: await backend_ws.send(msg) async def forward_to_frontend(): async for msg in backend_ws: await websocket.send(msg) await asyncio.gather( forward_to_backend(), forward_to_frontend() ) except Exception as e: print(f"WebSocket proxy error: {e}") async def ws_handler(websocket, path): await websocket_proxy(websocket, path) def run_websocket_server(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(websockets.serve(ws_handler, "0.0.0.0", PORT)) loop.run_forever() if __name__ == "__main__": # Start WebSocket server in background thread ws_thread = threading.Thread(target=run_websocket_server, daemon=True) ws_thread.start() print(f"WebSocket proxy listening on ws://0.0.0.0:{PORT}") # Start HTTP server server = http.server.HTTPServer(("0.0.0.0", PORT), KVMHandler) print(f"Static file server serving on http://0.0.0.0:{PORT}") server.serve_forever()