File size: 4,655 Bytes
bb56382 971fa87 bb56382 971fa87 bb56382 971fa87 bb56382 971fa87 | 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 | """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
} |