serve.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. """Simple static file server for the KVM frontend with API proxy"""
  2. import http.server
  3. import urllib.request
  4. import urllib.error
  5. import os
  6. import json
  7. PORT = 8006
  8. DIST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dist")
  9. API_BASE = "http://127.0.0.1:8004"
  10. class KVMHandler(http.server.SimpleHTTPRequestHandler):
  11. def __init__(self, *args, **kwargs):
  12. super().__init__(*args, directory=DIST_DIR, **kwargs)
  13. def do_GET(self):
  14. if self.path.startswith("/api/"):
  15. self._proxy("GET")
  16. elif self.path == "/" or self.path == "":
  17. self.path = "/index.html"
  18. super().do_GET()
  19. else:
  20. # SPA fallback: if file doesn't exist, serve index.html
  21. file_path = os.path.join(DIST_DIR, self.path.lstrip("/"))
  22. if os.path.isfile(file_path):
  23. super().do_GET()
  24. else:
  25. self.path = "/index.html"
  26. super().do_GET()
  27. def do_POST(self):
  28. if self.path.startswith("/api/"):
  29. self._proxy("POST")
  30. else:
  31. self.send_error(404)
  32. def do_PUT(self):
  33. if self.path.startswith("/api/"):
  34. self._proxy("PUT")
  35. else:
  36. self.send_error(404)
  37. def do_DELETE(self):
  38. if self.path.startswith("/api/"):
  39. self._proxy("DELETE")
  40. else:
  41. self.send_error(404)
  42. def _proxy(self, method):
  43. """Proxy API requests to backend"""
  44. content_length = int(self.headers.get("Content-Length", 0))
  45. body = self.rfile.read(content_length) if content_length > 0 else None
  46. url = f"{API_BASE}{self.path}"
  47. req = urllib.request.Request(url, data=body, method=method)
  48. # Forward headers
  49. for key in ["Content-Type", "Authorization"]:
  50. if key in self.headers:
  51. req.add_header(key, self.headers[key])
  52. try:
  53. with urllib.request.urlopen(req, timeout=30) as resp:
  54. resp_body = resp.read()
  55. self.send_response(resp.status)
  56. self.send_header("Content-Type", "application/json")
  57. self.send_header("Access-Control-Allow-Origin", "*")
  58. self.end_headers()
  59. self.wfile.write(resp_body)
  60. except urllib.error.HTTPError as e:
  61. resp_body = e.read()
  62. self.send_response(e.code)
  63. self.send_header("Content-Type", "application/json")
  64. self.send_header("Access-Control-Allow-Origin", "*")
  65. self.end_headers()
  66. self.wfile.write(resp_body)
  67. except Exception as e:
  68. self.send_response(502)
  69. self.send_header("Content-Type", "application/json")
  70. self.end_headers()
  71. self.wfile.write(json.dumps({"error": str(e)}).encode())
  72. def log_message(self, format, *args):
  73. pass # Suppress logs
  74. if __name__ == "__main__":
  75. server = http.server.HTTPServer(("0.0.0.0", PORT), KVMHandler)
  76. print(f"KVM Frontend serving on http://0.0.0.0:{PORT}")
  77. server.serve_forever()