mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 15:41:41 +02:00
171 lines
5.3 KiB
Python
171 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
from contextlib import AbstractContextManager
|
|
from dataclasses import dataclass
|
|
from functools import partial
|
|
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
import json
|
|
from pathlib import Path
|
|
import threading
|
|
from typing import Any
|
|
|
|
from .config import GatewayConfig, ProtocolConfig, SiteConfig
|
|
from .manifest import fixture_manifest
|
|
|
|
|
|
PREVIEW_MANIFEST_NAME = "ampg-preview-fixture-manifest.json"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PreviewEndpoint:
|
|
site_id: str
|
|
protocol: str
|
|
renderer: str
|
|
root: Path
|
|
host: str
|
|
port: int
|
|
url: str
|
|
status: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PreviewManifestWriteResult:
|
|
site_id: str
|
|
path: Path
|
|
fixture_count: int
|
|
|
|
|
|
def preview_endpoints(
|
|
config: GatewayConfig,
|
|
*,
|
|
base_port: int = 19080,
|
|
host: str = "127.0.0.1",
|
|
) -> list[PreviewEndpoint]:
|
|
endpoints: list[PreviewEndpoint] = []
|
|
offset = 0
|
|
for site in config.sites:
|
|
for protocol in site.protocols.values():
|
|
if not protocol.enabled:
|
|
continue
|
|
port = int(protocol.options.get("preview_port", base_port + offset))
|
|
root = site.outputs.root / protocol.name
|
|
status = "ready" if (root / ".ampg-output").exists() else "missing-output"
|
|
endpoints.append(
|
|
PreviewEndpoint(
|
|
site_id=site.id,
|
|
protocol=protocol.name,
|
|
renderer=protocol.renderer,
|
|
root=root,
|
|
host=host,
|
|
port=port,
|
|
url=f"http://{host}:{port}/",
|
|
status=status,
|
|
)
|
|
)
|
|
offset += 1
|
|
return endpoints
|
|
|
|
|
|
def preview_fixture_manifest(
|
|
config: GatewayConfig,
|
|
site: SiteConfig,
|
|
endpoints: list[PreviewEndpoint],
|
|
) -> dict[str, Any]:
|
|
by_protocol = {
|
|
endpoint.protocol: endpoint
|
|
for endpoint in endpoints
|
|
if endpoint.site_id == site.id
|
|
}
|
|
manifest = fixture_manifest(config, site)
|
|
manifest["mode"] = "preview"
|
|
for fixture in manifest["fixtures"]:
|
|
endpoint = by_protocol[fixture["protocol"]]
|
|
published_url = fixture["url"]
|
|
published_checks = dict(fixture["checks"])
|
|
fixture_path = fixture.get("route", {}).get("fixture_path", "/")
|
|
fixture["published"] = {
|
|
"url": published_url,
|
|
"checks": published_checks,
|
|
"address_status": fixture["address_status"],
|
|
}
|
|
fixture["preview"] = {
|
|
"mode": "loopback-http",
|
|
"root": str(endpoint.root),
|
|
"status": endpoint.status,
|
|
}
|
|
fixture["url"] = _preview_url(endpoint.url, fixture_path)
|
|
fixture["address_status"] = "preview"
|
|
fixture["checks"] = {
|
|
"transport": "clearnet",
|
|
"context": "clearnet",
|
|
"isolation": "transport-context",
|
|
}
|
|
return manifest
|
|
|
|
|
|
def write_preview_fixture_manifests(
|
|
config: GatewayConfig,
|
|
*,
|
|
base_port: int = 19080,
|
|
host: str = "127.0.0.1",
|
|
) -> list[PreviewManifestWriteResult]:
|
|
endpoints = preview_endpoints(config, base_port=base_port, host=host)
|
|
results: list[PreviewManifestWriteResult] = []
|
|
for site in config.sites:
|
|
manifest = preview_fixture_manifest(config, site, endpoints)
|
|
path = preview_manifest_path(site)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
results.append(
|
|
PreviewManifestWriteResult(
|
|
site_id=site.id,
|
|
path=path,
|
|
fixture_count=len(manifest["fixtures"]),
|
|
)
|
|
)
|
|
return results
|
|
|
|
|
|
def preview_manifest_path(site: SiteConfig) -> Path:
|
|
return site.outputs.root / PREVIEW_MANIFEST_NAME
|
|
|
|
|
|
class PreviewServers(AbstractContextManager):
|
|
def __init__(self, endpoints: list[PreviewEndpoint]) -> None:
|
|
self.endpoints = endpoints
|
|
self._servers: list[ThreadingHTTPServer] = []
|
|
self._threads: list[threading.Thread] = []
|
|
|
|
def __enter__(self) -> "PreviewServers":
|
|
for endpoint in self.endpoints:
|
|
if endpoint.status != "ready":
|
|
raise FileNotFoundError(
|
|
f"{endpoint.site_id}/{endpoint.protocol}: output root is not built: {endpoint.root}"
|
|
)
|
|
handler = partial(SimpleHTTPRequestHandler, directory=str(endpoint.root))
|
|
server = ThreadingHTTPServer((endpoint.host, endpoint.port), handler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
self._servers.append(server)
|
|
self._threads.append(thread)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
|
for server in self._servers:
|
|
server.shutdown()
|
|
server.server_close()
|
|
for thread in self._threads:
|
|
thread.join(timeout=2)
|
|
|
|
def wait_forever(self) -> None:
|
|
try:
|
|
threading.Event().wait()
|
|
except KeyboardInterrupt:
|
|
return
|
|
|
|
|
|
def _preview_url(base_url: str, fixture_path: str) -> str:
|
|
if not fixture_path or fixture_path == "/":
|
|
return base_url
|
|
return base_url.rstrip("/") + "/" + fixture_path.lstrip("/")
|