Add bridge API server (GET /session-id, POST /send) on port 8082
This commit is contained in:
117
api_server.py
Normal file
117
api_server.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""Simple HTTP API for the Matrix Bridge.
|
||||
|
||||
Provides endpoints for agents and scheduled tasks to interact
|
||||
with the bridge without going through the Matrix protocol.
|
||||
|
||||
Endpoints:
|
||||
GET /session-id — Return the current bridge session ID
|
||||
POST /send — Send a text message to the configured Matrix room
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bridge import Bridge
|
||||
|
||||
log = logging.getLogger("matrix-bridge.api")
|
||||
|
||||
|
||||
class _Handler(BaseHTTPRequestHandler):
|
||||
"""HTTP handler that delegates to a shared bridge reference."""
|
||||
|
||||
# Set once before the server starts — shared across all requests.
|
||||
bridge: Optional["Bridge"] = None
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────
|
||||
|
||||
def _send_json(self, status: int, data: dict) -> None:
|
||||
"""Send a JSON response with the given status code."""
|
||||
body = json.dumps(data).encode("utf-8")
|
||||
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)
|
||||
|
||||
def _read_body(self) -> Optional[dict]:
|
||||
"""Read and parse the request body as JSON."""
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
if length <= 0:
|
||||
return None
|
||||
raw = self.rfile.read(length)
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
# ── Routes ────────────────────────────────────────────────────────
|
||||
|
||||
def do_GET(self) -> None:
|
||||
if self.path == "/session-id":
|
||||
if self.bridge is None:
|
||||
self._send_json(503, {"error": "Bridge not initialized"})
|
||||
return
|
||||
sid = self.bridge.state.session_id
|
||||
self._send_json(200, {"session_id": sid})
|
||||
else:
|
||||
self._send_json(404, {"error": f"Not found: {self.path}"})
|
||||
|
||||
def do_POST(self) -> None:
|
||||
if self.path == "/send":
|
||||
if self.bridge is None:
|
||||
self._send_json(503, {"error": "Bridge not initialized"})
|
||||
return
|
||||
|
||||
body = self._read_body()
|
||||
if body is None:
|
||||
self._send_json(400, {"error": "Invalid or missing JSON body"})
|
||||
return
|
||||
|
||||
message = body.get("message")
|
||||
if not message or not isinstance(message, str):
|
||||
self._send_json(400, {"error": "Missing or invalid 'message' field"})
|
||||
return
|
||||
|
||||
success = self.bridge.matrix.send_message(message)
|
||||
if success:
|
||||
self._send_json(200, {"ok": True})
|
||||
else:
|
||||
self._send_json(500, {"error": "Failed to send Matrix message"})
|
||||
else:
|
||||
self._send_json(404, {"error": f"Not found: {self.path}"})
|
||||
|
||||
# ── Silence default logging ───────────────────────────────────────
|
||||
|
||||
def log_message(self, format: str, *args) -> None:
|
||||
log.debug(f"HTTP: {format % args}")
|
||||
|
||||
|
||||
def start_api_server(
|
||||
bridge: "Bridge", host: str = "127.0.0.1", port: int = 8082
|
||||
) -> None:
|
||||
"""Start the API HTTP server in a background daemon thread.
|
||||
|
||||
The server runs until the main process exits. It provides a
|
||||
lightweight JSON API on ``host:port`` for other components to
|
||||
query the bridge state or trigger Matrix messages.
|
||||
"""
|
||||
_Handler.bridge = bridge
|
||||
server = HTTPServer((host, port), _Handler)
|
||||
server.timeout = 1.0 # Allows clean shutdown without dedicated stop
|
||||
|
||||
thread = threading.Thread(
|
||||
target=server.serve_forever,
|
||||
name=f"api-{host}:{port}",
|
||||
daemon=True,
|
||||
)
|
||||
thread.start()
|
||||
log.info(f"API server listening on http://{host}:{port}")
|
||||
@@ -16,6 +16,8 @@
|
||||
"agent_retries": 3,
|
||||
"max_message_length": 40000,
|
||||
"processed_ids_limit": 200,
|
||||
"agent_response_timeout": 300
|
||||
"agent_response_timeout": 300,
|
||||
"api_host": "127.0.0.1",
|
||||
"api_port": 8082
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ class BridgeConfig:
|
||||
max_message_length: int = 40000
|
||||
processed_ids_limit: int = 200
|
||||
agent_response_timeout: int = 300
|
||||
api_host: str = "127.0.0.1"
|
||||
api_port: int = 8082
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
4
main.py
4
main.py
@@ -20,6 +20,7 @@ import sys
|
||||
|
||||
from config import Config
|
||||
from bridge import Bridge
|
||||
from api_server import start_api_server
|
||||
|
||||
|
||||
def setup_logging(debug: bool = False) -> None:
|
||||
@@ -61,6 +62,9 @@ def main() -> None:
|
||||
# Create and run the bridge
|
||||
bridge = Bridge(config)
|
||||
|
||||
# Start the API server (daemon thread, non-blocking)
|
||||
start_api_server(bridge, config.bridge.api_host, config.bridge.api_port)
|
||||
|
||||
# Verify agent API is reachable
|
||||
if bridge.agent._request("GET", "/api/sessions") is not None:
|
||||
log.info("Agent API is reachable")
|
||||
|
||||
Reference in New Issue
Block a user