"""HTTP-GET mit DNS-Rückfall für Health-Checks. Der Resolver des Admin-Servers (Coolify/Docker) cacht „Name nicht gefunden“ für neue Hostnamen oft lange – obwohl der Name öffentlich längst existiert. Schlägt die Auflösung fehl, fragen wir die Adresse per DNS-over-HTTPS (Cloudflare, dann Google) ab und verbinden uns direkt zur IP (Host-Header + SNI = Hostname, Zertifikat wird normal geprüft). """ from __future__ import annotations import socket from urllib.parse import urlsplit import httpx def _is_dns_error(exc: BaseException) -> bool: seen = set() while exc is not None and id(exc) not in seen: seen.add(id(exc)) if isinstance(exc, socket.gaierror): return True exc = exc.__cause__ or exc.__context__ return False def is_dns_failure_text(msg: str) -> bool: m = msg.lower() return "name or service not known" in m or "temporary failure in name resolution" in m or "nodename nor servname" in m or "gaierror" in m def doh_resolve(host: str, timeout: float = 5.0) -> list[str]: for url in ("https://cloudflare-dns.com/dns-query", "https://dns.google/resolve"): try: r = httpx.get(url, params={"name": host, "type": "A"}, headers={"accept": "application/dns-json"}, timeout=timeout) ips = [a["data"] for a in r.json().get("Answer", []) if a.get("type") == 1] if ips: return ips except Exception: # noqa: BLE001 continue return [] def get_json(url: str, timeout: float = 8.0) -> tuple[int, dict]: """GET mit DNS-Rückfall → (status, json-dict oder {}). Bei Rückfall enthält das Dict `_dns_fallback: true`.""" def parse(r): return r.status_code, (r.json() if r.headers.get("content-type", "").startswith("application/json") else {}) try: return parse(httpx.get(url, timeout=timeout, follow_redirects=True)) except httpx.ConnectError as exc: if not _is_dns_error(exc): raise parts = urlsplit(url) for ip in doh_resolve(parts.hostname or ""): try: r = httpx.get(f"{parts.scheme}://{ip}{parts.path or '/'}", headers={"Host": parts.hostname}, extensions={"sni_hostname": parts.hostname}, timeout=timeout, follow_redirects=True) status, body = parse(r) return status, {**body, "_dns_fallback": True} except Exception: # noqa: BLE001 continue raise