Spaces:
Runtime error
Runtime error
File size: 5,363 Bytes
76b253b bab413c 07f8298 76b253b bab413c 07f8298 bab413c 07f8298 76b253b 07f8298 bab413c 07f8298 76b253b 07f8298 bab413c 07f8298 bab413c 07f8298 bab413c 07f8298 bab413c 07f8298 bab413c 07f8298 bab413c b0bf7d8 bab413c b0bf7d8 bab413c b0bf7d8 bab413c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# PATH: bot/integrations/http.py
import ssl
import json as _json
from typing import Any, Dict, Optional, Tuple, List
from urllib.parse import urlparse, urlunparse
import httpx
_DEFAULT_TIMEOUT = httpx.Timeout(20.0, connect=10.0)
_client: Optional[httpx.AsyncClient] = None
_client_insecure: Optional[httpx.AsyncClient] = None
def get_client() -> httpx.AsyncClient:
global _client
if _client is None:
_client = httpx.AsyncClient(
timeout=_DEFAULT_TIMEOUT,
follow_redirects=True,
http2=True,
)
return _client
def get_client_insecure() -> httpx.AsyncClient:
"""
Insecure client used only for IP fallback (TLS verify off),
because we connect to https://<IP> with Host header.
"""
global _client_insecure
if _client_insecure is None:
_client_insecure = httpx.AsyncClient(
timeout=_DEFAULT_TIMEOUT,
follow_redirects=True,
http2=True,
verify=False,
)
return _client_insecure
def _is_dns_error(e: Exception) -> bool:
s = str(e).lower()
return ("no address associated with hostname" in s) or ("gaierror" in s) or ("name or service not known" in s)
async def doh_resolve_a(host: str) -> List[str]:
"""
Resolve A records via DNS-over-HTTPS.
Uses dns.google (since google.com resolves fine in your env).
"""
c = get_client()
try:
r = await c.get("https://dns.google/resolve", params={"name": host, "type": "A"})
if r.status_code != 200:
return []
j = r.json()
ans = j.get("Answer") or []
ips = []
for a in ans:
if a.get("type") == 1 and a.get("data"):
ips.append(str(a["data"]))
# unique keep order
out = []
for ip in ips:
if ip not in out:
out.append(ip)
return out
except Exception:
return []
def _split_url(url: str) -> Tuple[str, str, str]:
"""
Returns (scheme, host, rest(path+query+fragment))
"""
u = urlparse(url)
scheme = u.scheme or "https"
host = u.netloc
rest = urlunparse(("", "", u.path or "/", u.params, u.query, u.fragment))
return scheme, host, rest
async def fetch_status(url: str) -> str:
"""
Returns status code as string, or ERR:...
Uses same DNS fallback logic.
"""
try:
r = await request_text("GET", url)
return str(r[0])
except Exception as e:
return f"ERR:{type(e).__name__}:{e}"
async def request_text(method: str, url: str, headers: Optional[Dict[str, str]] = None) -> Tuple[int, str]:
"""
Low-level text request with DNS fallback.
Returns (status_code, text)
"""
headers = dict(headers or {})
c = get_client()
try:
r = await c.request(method, url, headers=headers)
return r.status_code, r.text
except Exception as e:
# DNS fail -> DoH fallback
if not _is_dns_error(e):
raise
scheme, host, rest = _split_url(url)
if not host:
raise
ips = await doh_resolve_a(host)
if not ips:
raise
ic = get_client_insecure()
last_err = e
for ip in ips:
try:
# connect to IP but keep routing via Host header
h2 = dict(headers)
h2["Host"] = host
r = await ic.request(method, f"{scheme}://{ip}{rest}", headers=h2)
return r.status_code, r.text
except Exception as e2:
last_err = e2
continue
raise last_err
async def post_json(url: str, headers: Optional[Dict[str, str]] = None, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""
POST JSON helper with DNS fallback.
Returns dict: {ok, status, data?, err?}
"""
headers = dict(headers or {})
headers.setdefault("Content-Type", "application/json")
c = get_client()
payload = payload or {}
try:
r = await c.post(url, headers=headers, json=payload)
try:
data = r.json()
except Exception:
data = {"raw": r.text}
return {"ok": r.status_code < 400, "status": r.status_code, "data": data}
except Exception as e:
if not _is_dns_error(e):
return {"ok": False, "status": 0, "err": str(e)}
# DNS fail -> DoH -> IP fallback (verify off + Host header)
scheme, host, rest = _split_url(url)
if not host:
return {"ok": False, "status": 0, "err": str(e)}
ips = await doh_resolve_a(host)
if not ips:
return {"ok": False, "status": 0, "err": str(e)}
ic = get_client_insecure()
last_err: Exception = e
for ip in ips:
try:
h2 = dict(headers)
h2["Host"] = host
r = await ic.post(f"{scheme}://{ip}{rest}", headers=h2, json=payload)
try:
data = r.json()
except Exception:
data = {"raw": r.text}
return {"ok": r.status_code < 400, "status": r.status_code, "data": data}
except Exception as e2:
last_err = e2
continue
return {"ok": False, "status": 0, "err": str(last_err)} |