mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-07 18:04:59 +03:00
Раньше ProxyHandler-логика дублировалась в openrouter/checks/transports и поддерживала только http/https. Telegram/SOCKS-сервисы требуют SOCKS5 — теперь единый build_opener поддерживает http/https/socks5/socks5h во всех интеграциях. - integrations/proxy.py (новый): build_opener(proxy_url) — http/https через stdlib ProxyHandler; socks5/socks5h через кастомный _SocksProxyHandler (PySocks, изолированный от остального процесса: переопределённый .connect, не глобальный monkeypatch socket). socks5h → DNS через прокси (rdns=True). Без PySocks при SOCKS-схеме — ValueError с явным сообщением. - openrouter.py / checks.py / transports/base.py: убрано дублирование ProxyHandler, все используют build_opener (chat, embeddings, check_*, MAX/Telegram transport). - requirements.txt: PySocks>=1.7. - IntegrationForm.tsx: placeholder упоминает socks5://. - tests.py: build_opener socks-схема строит handler; http → ProxyHandler; без PySocks → ValueError; check_*/OpenRouter прокидывают proxy_url (вкл socks5).
108 lines
4.2 KiB
Python
108 lines
4.2 KiB
Python
"""Единый HTTP-opener с поддержкой прокси для всех интеграций (ADR-HUB-0020).
|
|
|
|
Поддерживаемые схемы proxy_url:
|
|
- http://[user:pass@]host:port
|
|
- https://[user:pass@]host:port
|
|
- socks5://[user:pass@]host:port (DNS резолвится локально)
|
|
- socks5h://[user:pass@]host:port (DNS через прокси, rdns=True)
|
|
|
|
HTTP/HTTPS работают через stdlib ProxyHandler. SOCKS5 требует PySocks (`socks`):
|
|
если зависимость отсутствует — SOCKS-схема даёт ValueError, чтобы владелец видел
|
|
явную ошибку, а не молчаливый обход прокси.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import http.client
|
|
import ssl
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
SOCKS_SCHEMES = ("socks5", "socks5h")
|
|
|
|
|
|
def build_opener(proxy_url: str):
|
|
"""urllib opener, проксирующий http/https/socks5 запросы. Пустой proxy_url → без прокси."""
|
|
if not proxy_url:
|
|
return urllib.request.build_opener()
|
|
scheme = urllib.parse.urlparse(proxy_url).scheme.lower()
|
|
if scheme in SOCKS_SCHEMES:
|
|
return urllib.request.build_opener(_SocksProxyHandler(proxy_url))
|
|
return urllib.request.build_opener(urllib.request.ProxyHandler({"http": proxy_url, "https": proxy_url}))
|
|
|
|
|
|
class _SocksProxyHandler(urllib.request.HTTPHandler, urllib.request.HTTPSHandler):
|
|
"""urllib handler, устанавливающий TCP-соединение через SOCKS5 (PySocks).
|
|
|
|
PySocks патчит socket.create_connection глобально; мы вместо этого строим
|
|
http.client-соединение с переопределённым .connect(), который туннелирует
|
|
через SOCKS — это изолирует прокси от остального процесса.
|
|
"""
|
|
|
|
def __init__(self, proxy_url: str):
|
|
super().__init__()
|
|
self._socks = _import_socks()
|
|
parsed = urllib.parse.urlparse(proxy_url)
|
|
self._proxy_type = self._socks.PROXY_TYPE_SOCKS5
|
|
self._proxy_host = parsed.hostname or ""
|
|
self._proxy_port = parsed.port or 1080
|
|
self._username = parsed.username or None
|
|
self._password = parsed.password or None
|
|
# socks5h → DNS резолвится на стороне прокси (rdns=True).
|
|
self._rdns = parsed.scheme.lower() == "socks5h"
|
|
|
|
def http_open(self, req):
|
|
return self.do_open(self._make_http_conn, req)
|
|
|
|
def https_open(self, req):
|
|
return self.do_open(self._make_https_conn, req)
|
|
|
|
def _make_http_conn(self, req, timeout=30):
|
|
host = req.host
|
|
port = req.port or 80
|
|
return _SocksHTTPConnection(host, port, self, timeout=timeout)
|
|
|
|
def _make_https_conn(self, req, timeout=30):
|
|
host = req.host
|
|
port = req.port or 443
|
|
return _SocksHTTPSConnection(host, port, self, timeout=timeout, context=ssl.create_default_context())
|
|
|
|
def open_socket(self, host: str, port: int):
|
|
return self._socks.create_connection(
|
|
(host, port),
|
|
proxy_type=self._proxy_type,
|
|
proxy_addr=self._proxy_host,
|
|
proxy_port=self._proxy_port,
|
|
proxy_username=self._username,
|
|
proxy_password=self._password,
|
|
rdns=self._rdns,
|
|
)
|
|
|
|
|
|
def _import_socks():
|
|
try:
|
|
import socks
|
|
|
|
return socks
|
|
except ImportError as error:
|
|
raise ValueError("Для SOCKS-прокси нужна зависимость PySocks (pip install PySocks)") from error
|
|
|
|
|
|
class _SocksHTTPConnection(http.client.HTTPConnection):
|
|
def __init__(self, host, port, handler, timeout=30):
|
|
super().__init__(host, port, timeout=timeout)
|
|
self._handler = handler
|
|
|
|
def connect(self):
|
|
self.sock = self._handler.open_socket(self.host, self.port)
|
|
|
|
|
|
class _SocksHTTPSConnection(http.client.HTTPSConnection):
|
|
def __init__(self, host, port, handler, timeout=30, context=None):
|
|
super().__init__(host, port, timeout=timeout, context=context)
|
|
self._handler = handler
|
|
|
|
def connect(self):
|
|
sock = self._handler.open_socket(self.host, self.port)
|
|
self.sock = self._context.wrap_socket(sock, server_hostname=self.host)
|