#!/usr/bin/env python3 """Internal protocol-level webhook receiver for the local alert drill.""" from __future__ import annotations import json from datetime import UTC, datetime from http import HTTPStatus from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from threading import Lock from typing import ClassVar class AlertReceiver(BaseHTTPRequestHandler): events: ClassVar[list[dict[str, object]]] = [] events_lock: ClassVar[Lock] = Lock() def do_GET(self) -> None: if self.path == "/healthz": self._send_json(HTTPStatus.OK, {"status": "ok"}) return if self.path == "/events": with self.events_lock: snapshot = list(self.events) self._send_json(HTTPStatus.OK, snapshot) return self._send_json(HTTPStatus.NOT_FOUND, {"error": "not_found"}) def do_POST(self) -> None: if self.path != "/alerts": self._send_json(HTTPStatus.NOT_FOUND, {"error": "not_found"}) return try: content_length = int(self.headers.get("Content-Length", "")) raw_payload = self.rfile.read(content_length) payload = json.loads(raw_payload) except (TypeError, ValueError, json.JSONDecodeError): self._send_json(HTTPStatus.BAD_REQUEST, {"error": "invalid_json"}) return if not isinstance(payload, dict) or not isinstance(payload.get("alerts"), list): self._send_json(HTTPStatus.UNPROCESSABLE_ENTITY, {"error": "invalid_alert_payload"}) return event = { "received_at": datetime.now(UTC).isoformat(), "payload": payload, } with self.events_lock: self.events.append(event) self.send_response(HTTPStatus.NO_CONTENT) self.end_headers() def log_message(self, message: str, *args: object) -> None: print( f"{self.log_date_time_string()} {self.client_address[0]} " f"{message % args}", flush=True, ) def _send_json(self, status: HTTPStatus, payload: object) -> None: body = json.dumps(payload, separators=(",", ":")).encode() self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) if __name__ == "__main__": ThreadingHTTPServer(("0.0.0.0", 8080), AlertReceiver).serve_forever()