forked from luopingyi/xc_agent_platform_demo
84 lines
2.4 KiB
Python
84 lines
2.4 KiB
Python
|
|
"""Minimal HTTP server for the adaptation agent (stdlib only).
|
||
|
|
|
||
|
|
Listens on PORT (default 8080). Handles SIGTERM and exits the main loop
|
||
|
|
within ~1s (platform window documented as ≤30s).
|
||
|
|
|
||
|
|
Startup: require STRATEGY_ID (Phase1 product plan — fail if missing).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import os
|
||
|
|
import signal
|
||
|
|
import sys
|
||
|
|
import threading
|
||
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
from .health import handle_get
|
||
|
|
from .strategy import require_strategy_id
|
||
|
|
|
||
|
|
_shutdown = threading.Event()
|
||
|
|
_httpd: Optional[ThreadingHTTPServer] = None
|
||
|
|
|
||
|
|
|
||
|
|
class AgentHandler(BaseHTTPRequestHandler):
|
||
|
|
protocol_version = "HTTP/1.1"
|
||
|
|
|
||
|
|
def log_message(self, fmt: str, *args) -> None: # noqa: A003
|
||
|
|
# Avoid noisy default logging; never log env secrets.
|
||
|
|
sys.stderr.write("[http] request handled (details omitted)\n")
|
||
|
|
|
||
|
|
def do_GET(self) -> None: # noqa: N802
|
||
|
|
code, headers, body = handle_get(self.path)
|
||
|
|
self.send_response(code)
|
||
|
|
for k, v in headers.items():
|
||
|
|
self.send_header(k, v)
|
||
|
|
self.send_header("Content-Length", str(len(body)))
|
||
|
|
self.end_headers()
|
||
|
|
self.wfile.write(body)
|
||
|
|
|
||
|
|
def do_POST(self) -> None: # noqa: N802
|
||
|
|
body = b'{"error":"method_not_allowed"}'
|
||
|
|
self.send_response(405)
|
||
|
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||
|
|
self.send_header("Content-Length", str(len(body)))
|
||
|
|
self.end_headers()
|
||
|
|
self.wfile.write(body)
|
||
|
|
|
||
|
|
|
||
|
|
def _on_sigterm(signum: int, frame) -> None: # noqa: ARG001
|
||
|
|
sys.stderr.write(f"Received signal {signum}; shutting down...\n")
|
||
|
|
_shutdown.set()
|
||
|
|
httpd = _httpd
|
||
|
|
if httpd is not None:
|
||
|
|
# wake serve_forever
|
||
|
|
threading.Thread(target=httpd.shutdown, daemon=True).start()
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
global _httpd
|
||
|
|
|
||
|
|
require_strategy_id() # exit 1 if missing
|
||
|
|
|
||
|
|
port = int(os.environ.get("PORT", "8080"))
|
||
|
|
host = os.environ.get("HOST", "0.0.0.0")
|
||
|
|
|
||
|
|
signal.signal(signal.SIGTERM, _on_sigterm)
|
||
|
|
signal.signal(signal.SIGINT, _on_sigterm)
|
||
|
|
|
||
|
|
httpd = ThreadingHTTPServer((host, port), AgentHandler)
|
||
|
|
httpd.daemon_threads = True
|
||
|
|
_httpd = httpd
|
||
|
|
|
||
|
|
sys.stderr.write(f"Listening on {host}:{port} (GET /health, GET /status)\n")
|
||
|
|
try:
|
||
|
|
httpd.serve_forever(poll_interval=0.5)
|
||
|
|
finally:
|
||
|
|
httpd.server_close()
|
||
|
|
sys.stderr.write("Server stopped.\n")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|