bot_host / proxy_client.py
ItsBounvy's picture
Upload 24 files
bb56382 verified
Raw History Blame Contribute Delete
4.66 kB
"""Panel-side wrapper that routes outbound HTTP through the Render proxy.
Why
---
HuggingFace Spaces share outbound IPs that several big services
(Telegram Bot API, OpenAI, Anthropic, etc.) routinely blacklist. The
panel itself sits on a HF Space — to call those APIs reliably we
relay through the small service in ``render_proxy/``.
Strategy
--------
If ``RENDER_PROXY_URL`` + ``RENDER_PROXY_TOKEN`` are configured we
transparently rewrite the host of outgoing requests:
* the panel still builds normal URLs (``https://api.telegram.org/...``),
* we intercept just before sending and rewrite the call to go through
``https://<proxy>/forward?target=<encoded original URL>`` with a
bearer token. Response is returned to the caller as if direct.
For per-bot calls, the bot itself uses the values from env vars
``LLM_PROXY_URL`` / ``TELEGRAM_PROXY_URL`` (set by the panel during
deploy).
"""
from __future__ import annotations
import logging
from typing import Any, Optional
import httpx
from config import PANEL_CONFIG
log = logging.getLogger("panel.proxy-client")
def render_proxy_enabled() -> bool:
return bool(PANEL_CONFIG.render_proxy_url and PANEL_CONFIG.render_proxy_token)
def _rewrite_via_proxy(url: str) -> str:
"""Return ``url`` rewritten through the proxy's /forward endpoint."""
from urllib.parse import quote
proxy = PANEL_CONFIG.render_proxy_url.rstrip("/")
return f"{proxy}/forward?target={quote(url, safe='')}"
def _auth_headers() -> dict:
return {"Authorization": f"Bearer {PANEL_CONFIG.render_proxy_token}"}
def proxy_aware_request(
method: str,
url: str,
*,
headers: Optional[dict] = None,
json: Any = None,
data: Any = None,
params: Optional[dict] = None,
timeout: float = 60.0,
follow_redirects: bool = True,
) -> httpx.Response:
"""Make an HTTP request, routed through the Render proxy when set.
The caller passes a real external URL. If the proxy is enabled
the URL is rewritten; the request body and headers are otherwise
identical to a direct call. Caller receives a normal
``httpx.Response`` and can ``.raise_for_status()`` / ``.json()``
just like usual.
"""
headers = dict(headers or {})
# When proxying, we drop the original Host header — the upstream
# is the proxy. httpx sets Host from the URL automatically.
target = _rewrite_via_proxy(url) if render_proxy_enabled() else url
if render_proxy_enabled():
# Carry the original target host in a header so the proxy
# can log which logical backend was hit (useful in Render logs).
from urllib.parse import urlparse
original_host = urlparse(url).hostname or ""
headers.setdefault("X-Panel-Original-Host", original_host)
headers.update(_auth_headers())
return httpx.request(
method=method,
url=target,
headers=headers,
json=json,
data=data,
params=params,
timeout=timeout,
follow_redirects=follow_redirects,
)
async def proxy_aware_async_request(
method: str,
url: str,
*,
headers: Optional[dict] = None,
json: Any = None,
data: Any = None,
params: Optional[dict] = None,
timeout: float = 60.0,
follow_redirects: bool = True,
) -> httpx.Response:
"""Async variant of :func:`proxy_aware_request`."""
headers = dict(headers or {})
target = _rewrite_via_proxy(url) if render_proxy_enabled() else url
if render_proxy_enabled():
from urllib.parse import urlparse
headers.setdefault("X-Panel-Original-Host", urlparse(url).hostname or "")
headers.update(_auth_headers())
async with httpx.AsyncClient(timeout=timeout, follow_redirects=follow_redirects) as client:
return await client.request(
method=method,
url=target,
headers=headers,
json=json,
data=data,
params=params,
)
def bot_env_extras() -> dict[str, str]:
"""Return the env-var dict the panel should write into each
bot's ``.env`` file when ``RENDER_PROXY_URL`` is set.
The bot uses ``TELEGRAM_PROXY_URL`` and ``LLM_PROXY_URL`` to call
the proxy instead of Telegram/LLM providers directly.
"""
if not render_proxy_enabled():
return {}
proxy = PANEL_CONFIG.render_proxy_url.rstrip("/")
return {
"RENDER_PROXY_URL": PANEL_CONFIG.render_proxy_url,
"RENDER_PROXY_TOKEN": PANEL_CONFIG.render_proxy_token,
"TELEGRAM_PROXY_URL": f"{proxy}/telegram",
"LLM_PROXY_URL": f"{proxy}/llm/openai", # sensible default
}