understanding commited on
Commit
07f8298
·
verified ·
1 Parent(s): 5776e4e

Update bot/integrations/http.py

Browse files
Files changed (1) hide show
  1. bot/integrations/http.py +105 -14
bot/integrations/http.py CHANGED
@@ -1,25 +1,116 @@
1
  # PATH: bot/integrations/http.py
 
 
 
2
  import httpx
3
- from bot.core.settings import HTTP_TIMEOUT_SEC, MAX_RETRIES
4
 
5
- _async_client: httpx.AsyncClient | None = None
 
 
6
 
7
  def get_client() -> httpx.AsyncClient:
8
- global _async_client
9
- if _async_client is None:
10
- _async_client = httpx.AsyncClient(timeout=HTTP_TIMEOUT_SEC, follow_redirects=True)
11
- return _async_client
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- async def post_json(url: str, headers: dict, payload: dict) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  c = get_client()
15
  last_err = None
16
- for _ in range(MAX_RETRIES + 1):
 
17
  try:
18
- r = await c.post(url, headers=headers, json=payload)
19
- data = r.json() if r.headers.get("content-type","").startswith("application/json") else {}
20
- if r.status_code >= 400:
21
- return {"ok": False, "status": r.status_code, "data": data, "text": r.text[:300]}
22
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  except Exception as e:
24
  last_err = e
25
- return {"ok": False, "status": 0, "err": str(last_err)}
 
 
 
 
 
 
1
  # PATH: bot/integrations/http.py
2
+ import asyncio
3
+ from typing import Any, Dict, Optional
4
+
5
  import httpx
 
6
 
7
+ # One global client (HF no persistence, but process lives)
8
+ _client: Optional[httpx.AsyncClient] = None
9
+
10
 
11
  def get_client() -> httpx.AsyncClient:
12
+ """
13
+ Shared HTTP client for Worker calls.
14
+ - Retries handled in request_json()
15
+ - trust_env=False avoids weird proxy/env issues on HF
16
+ """
17
+ global _client
18
+ if _client is not None:
19
+ return _client
20
+
21
+ timeout = httpx.Timeout(
22
+ connect=10.0,
23
+ read=60.0,
24
+ write=60.0,
25
+ pool=10.0,
26
+ )
27
+
28
+ limits = httpx.Limits(
29
+ max_connections=50,
30
+ max_keepalive_connections=20,
31
+ keepalive_expiry=30.0,
32
+ )
33
+
34
+ _client = httpx.AsyncClient(
35
+ timeout=timeout,
36
+ limits=limits,
37
+ follow_redirects=True,
38
+ http2=True,
39
+ trust_env=False,
40
+ headers={
41
+ "user-agent": "YouTubeLoaderHF/1.0",
42
+ "accept": "application/json,text/plain,*/*",
43
+ },
44
+ )
45
+ return _client
46
 
47
+
48
+ async def request_json(
49
+ method: str,
50
+ url: str,
51
+ *,
52
+ headers: Optional[Dict[str, str]] = None,
53
+ json_body: Optional[Dict[str, Any]] = None,
54
+ data: Optional[Any] = None,
55
+ retries: int = 4,
56
+ backoff_base: float = 0.6,
57
+ ) -> Dict[str, Any]:
58
+ """
59
+ Standard wrapper used by cf_worker1/cf_worker2.
60
+ Returns a dict always:
61
+ - on success: parsed JSON dict (or {"ok": True, "raw": "..."} if non-json)
62
+ - on error: {"ok": False, "status": <int>, "err": "<msg>"}
63
+
64
+ Handles DNS glitches like:
65
+ [Errno -5] No address associated with hostname
66
+ """
67
  c = get_client()
68
  last_err = None
69
+
70
+ for attempt in range(retries):
71
  try:
72
+ r = await c.request(
73
+ method.upper(),
74
+ url,
75
+ headers=headers,
76
+ json=json_body,
77
+ data=data,
78
+ )
79
+
80
+ # Try parse JSON always
81
+ try:
82
+ j = r.json()
83
+ except Exception:
84
+ txt = (r.text or "").strip()
85
+ j = {"ok": r.is_success, "raw": txt}
86
+
87
+ if r.is_success:
88
+ # if API didn't include ok, add it
89
+ if isinstance(j, dict) and "ok" not in j:
90
+ j["ok"] = True
91
+ return j
92
+
93
+ # non-2xx -> return error payload
94
+ if isinstance(j, dict):
95
+ # keep server error if present
96
+ j.setdefault("ok", False)
97
+ j.setdefault("status", r.status_code)
98
+ if "err" not in j:
99
+ j["err"] = f"http_{r.status_code}"
100
+ return j
101
+
102
+ return {"ok": False, "status": r.status_code, "err": f"http_{r.status_code}"}
103
+
104
+ except (httpx.ConnectError, httpx.ConnectTimeout, httpx.ReadTimeout, httpx.RemoteProtocolError) as e:
105
+ last_err = e
106
+ except OSError as e:
107
+ # DNS / low-level network issues (includes Errno -5)
108
+ last_err = e
109
  except Exception as e:
110
  last_err = e
111
+
112
+ # retry with exponential backoff
113
+ if attempt < retries - 1:
114
+ await asyncio.sleep(backoff_base * (2 ** attempt))
115
+
116
+ return {"ok": False, "status": 0, "err": str(last_err) if last_err else "unknown_error"}