50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
"""Disconnect-safe HTTP serving for the AI usage exporter."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from typing import Any
|
|
|
|
|
|
def make_handler(collector: Any, poller: Any) -> type[BaseHTTPRequestHandler]:
|
|
"""Build an HTTP handler bound to one collector and polling engine."""
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def _respond(self, status: int, payload: bytes, content_type: str) -> None:
|
|
self.send_response(status)
|
|
self.send_header("Content-Type", content_type)
|
|
self.send_header("Content-Length", str(len(payload)))
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
|
|
def do_GET(self) -> None: # noqa: N802
|
|
try:
|
|
if self.path == "/metrics":
|
|
self._respond(
|
|
200,
|
|
collector.render(),
|
|
"text/plain; version=0.0.4",
|
|
)
|
|
elif self.path == "/healthz":
|
|
healthy = poller.is_healthy()
|
|
self._respond(
|
|
200 if healthy else 503,
|
|
b"ok\n" if healthy else b"poller unhealthy\n",
|
|
"text/plain; charset=utf-8",
|
|
)
|
|
else:
|
|
self.send_error(404)
|
|
except (BrokenPipeError, ConnectionResetError):
|
|
return
|
|
|
|
def log_message(self, _format: str, *_args: object) -> None:
|
|
return
|
|
|
|
return Handler
|
|
|
|
|
|
class Server(ThreadingHTTPServer):
|
|
"""Threaded metrics server that does not retain disconnected clients."""
|
|
|
|
daemon_threads = True
|