DNS, TLS, скорость и SOCKS5 UDP
DNS, адреса и TCP
import os
from duckonet import HttpClient, NetworkOptions, ResolveRule
network = NetworkOptions(
resolve=(ResolveRule('example.com', 443, ('192.0.2.10',)),),
tcp_keepalive=True,
tcp_keepidle=30,
tcp_keepinterval=10,
tcp_keepcount=3,
)
with HttpClient(api_key=os.environ['DUCKONET_API_KEY'], network=network) as client:
response = client.get('https://example.com/')
192.0.2.10 в примере — адрес для документации; замените его адресом своего сервера с подходящим сертификатом.
ResolveRule(host, port, addresses) задаёт от 1 до 16 уникальных IPv4/IPv6-адресов для origin. addresses — кортеж строк без квадратных скобок и zone ID. ConnectRule(host, port, target_host, target_port) меняет точку подключения, сохраняя исходные Host, SNI, проверяемое имя сертификата и область cookies. Порты — 1–65535. Имена Unicode нормализуются общим IDNA-механизмом при создании нативной сессии.
NetworkOptions | По умолчанию | Значение |
|---|---|---|
resolve | () | До 64 ResolveRule; без повторяющихся origin. |
connect_to | () | До 64 ConnectRule; без повторяющихся origin. |
doh_url | None | HTTPS URL DNS-over-HTTPS; без userinfo, fragment и пробельных символов. Сертификат DoH-сервера проверяется. |
doh_bootstrap | () | До 16 уникальных IP-адресов DoH-сервера; требует doh_url. Без bootstrap имя DoH-сервера нужно разрешить обычным способом. |
interface | None | Явное имя сетевого интерфейса без специальных curl-префиксов. Поддержка зависит от ОС. |
source_ip | None | Явный локальный IP без скобок и zone ID. |
family | None | 'auto', 'ipv4' или 'ipv6'; исходный адрес не должен противоречить семейству. |
local_port | None | Локальный порт 1–65535. |
local_port_range | 1 | Число последовательных портов начиная с local_port; диапазон не выходит за 65535. |
tcp_keepalive | None | True включает, False отключает, None не задаёт отдельное переопределение. |
tcp_keepidle | None | Секунды до keepalive-пробы, 1–86400; требует tcp_keepalive=True. |
tcp_keepinterval | None | Секунды между пробами, 1–86400; требует tcp_keepalive=True. |
tcp_keepcount | None | Число проб, 1–255; требует tcp_keepalive=True. |
tcp_nodelay | None | Необязательное явное управление TCP_NODELAY. |
Опции неизменяемы. enabled показывает наличие явных настроек. Одновременные interface и source_ip отклоняются. DoH не совмещается с прокси, локальной привязкой или явным семейством; разрешающие origin-переопределения не совмещаются с маршрутом, где целевое DNS выполняет прокси. Это явные ошибки совместимости, а не повод перейти на прямое соединение. TCP-опции действуют на TCP-сокеты; они не эмулируют TCP другой ОС для QUIC.
Клиентский сертификат и SPKI
import os
from duckonet import HttpClient, ClientCertificate, TLSOptions
tls = TLSOptions(client_certificate=ClientCertificate('client.pem', 'client-key.pem'))
with HttpClient(api_key=os.environ['DUCKONET_API_KEY'], tls=tls) as client:
response = client.get('https://example.com/private')
ClientCertificate(certificate, private_key, password=None) принимает пути str/Path к PEM-цепочке сертификатов и PEM-ключу. Пароль зашифрованного ключа — строка; пути и пароль не показываются в repr. Конструирование объекта файлы не читает. При создании нативной сессии каждый файл должен быть обычным и не превышать 2 МиБ; пути Unicode поддерживаются. PKCS12, аппаратные ключи и системное хранилище сертификатов этим интерфейсом не предоставляются.
TLSOptions(client_certificate=None, spki_pins=()) добавляет до 16 уникальных пинов вида sha256//BASE64_SHA256_SPKI. После префикса находится канонический Base64 от 32 байтов SHA-256 публичного SPKI, а не хеш всего сертификата. Для смены ключа заранее включите допустимые старый и новый пины в один кортеж. Пиннинг дополняет обычную проверку CA и имени сервера; отключить её этими опциями нельзя. enabled показывает наличие клиентского сертификата или пинов.
Разные TLS-настройки создают изолированные сессии. Для ротации откройте новую сессию; уже выполняемый handshake не меняется. Не храните пароль ключа в исходном тексте и не публикуйте файлы ключа.
Ограничение скорости и диагностика
RateLimit(send_bytes_per_second=None, receive_bytes_per_second=None, burst_bytes=65536) ограничивает передачу тела в соответствующем направлении. Скорости — целые байты в секунду от 1 до 2**63-1; None оставляет направление без отдельного лимита. burst_bytes — допустимый короткий всплеск от 16384 до 65536. enabled сообщает, задана ли хотя бы одна скорость.
Для отправки учитываются байты тела приложения, для приёма — выдаваемые декодированные байты тела. Заголовки, TLS и канальные накладные расходы в этот лимит не входят. Это ограничитель потока данных, а не обещание точного физического bitrate. Намеренное ожидание лимита не считается простоем чтения, но общий таймаут продолжает идти. Ограниченные запросы используют отдельные соединения, чтобы не задерживать соседние HTTP/2 или HTTP/3 потоки. WebSocket с rate_limit не поддерживается.
Пример безопасного журнала:
import os
from duckonet import HttpClient, RateLimit
def completed(event):
print(event.method, event.origin, event.status_code)
with HttpClient(
api_key=os.environ['DUCKONET_API_KEY'],
rate_limit=RateLimit(receive_bytes_per_second=262144),
diagnostic_hook=completed,
) as client:
response = client.get('https://example.com/file')
HttpDiagnosticEvent содержит method, origin, status_code, protocol, from_cache, attempt_count, timings, socket_info, phase='complete'. Путь, query, заголовки и тело в событие не входят. timings — кортеж пар с длительностями либо None, где измерение отсутствует. Для обычного ответа и cache hit hook вызывается один раз; для потока — один раз при явном успешном stream.result(). Закрытие недочитанного потока не принуждает его дочитываться ради hook.
У синхронного клиента hook должен быть синхронным. Асинхронный клиент допускает обычную либо асинхронную функцию. Исключение hook передаётся вызывающему коду после освобождения ресурсов и не запускает повтор уже завершённого запроса.
SocketInfo содержит необязательные local_address, local_port, local_family, remote_address, remote_port, remote_family, а также proxy_route=False и observation='connected-transport-socket'. Семейство — 4 или 6. Это адреса наблюдаемого транспортного сокета: при прокси удалённая сторона может быть прокси, а не origin. Отсутствующие сведения остаются None; библиотека не подставляет предполагаемый адрес сервера.
Ограничение скорости относится к телу отдельного запроса. Совместимость настроек учитывается при создании транспорта; одновременные запросы могут использовать отдельные соединения. Не рассчитывайте на общий лимит всего процесса или на обязательное мультиплексирование всех запросов в одном TCP/QUIC-соединении.
HTTP/3 через SOCKS5 UDP
import os
from duckonet import HttpClient, Socks5UDPProxy
proxy = Socks5UDPProxy('socks5h://127.0.0.1:1080')
with HttpClient(
api_key=os.environ['DUCKONET_API_KEY'],
protocol='h3',
proxy=proxy,
) as client:
response = client.get('https://example.com/')
Socks5UDPProxy(endpoint, username='', password='') явно выбирает UDP ASSOCIATE. socks5:// разрешает имя целевого сервера локально, socks5h:// передаёт его прокси. Endpoint содержит схему, адрес и порт; логин и пароль передаются отдельными полями. Свойство udp_associate равно True.
Нужен прокси с поддержкой UDP ASSOCIATE и разрешённым UDP-трафиком. Маршрут требует строгий HTTP/3: protocol='h3' на высоком уровне либо application='h3_only'; обычный ProxyRoute не переключается на этот режим автоматически. Обрыв управления TCP завершает ассоциацию. Ошибка реле, блокировка UDP, неверные адреса и фрагментированные SOCKS-датаграммы не вызывают прямой fallback. Доменный адрес самого реле разрешается локально независимо от настройки DNS целевого сервера.
HTTP/HTTPS/SOCKS TCP и существующий MasqueProxy остаются отдельными вариантами маршрута. Socks5UDPProxy не добавляет WebSocket поверх HTTP/3.