forked from luopingyi/xc_agent_platform_demo
Phase1 skeleton: replace demo main.py with src.server contract
- Overlay ModelHub XC Phase1 (health/status, STRATEGY_ID gate, dry-run) - Dockerfile CMD -> python -m src.server; FROM stays platform TCR base - Remove demo main.py and requirements.txt (stdlib only) - Add tests/, scripts/, docs/, Makefile, config/examples (no secrets)
This commit is contained in:
83
src/server.py
Normal file
83
src/server.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user