DuckoNet: HTTP/3 к сайту через MASQUE
HTTP/3 к сайту через MASQUE
MASQUE задаётся явно объектом MasqueProxy, импортируемым из duckonet:
MasqueProxy(endpoint, username="", password="", carrier="h2",
uri_template="/.well-known/masque/udp/{target_host}/{target_port}/")
endpoint — HTTPS URL прокси, без пути (кроме /), query и fragment. Credentials передавайте отдельными полями, а не в endpoint. HTTP API нормализует имя хоста и добавляет порт 443, если он пропущен. Для учётных данных поддерживается Basic authentication. Имя пользователя/пароль скрыты из repr, но сами объекты и ответы не следует произвольно журналировать.
import os
from duckonet import HttpClient, MasqueProxy
proxy = MasqueProxy(
"https://proxy.example:443",
username=os.environ.get("MASQUE_USER", ""),
password=os.environ.get("MASQUE_PASSWORD", ""),
)
with HttpClient(api_key=os.environ["DUCKONET_API_KEY"], browser="chrome",
proxy=proxy, protocol="h3") as client:
if not client.capabilities.http3_proxy:
raise RuntimeError("Выбранный runtime/profile не предоставляет HTTP3 proxy")
response = client.get("https://origin.example/data")
response.raise_for_status()
print(response.status_code, response.protocol)
Можно выбрать маршрут для одного запроса: client.get(url, proxy=proxy, protocol="h3"). Поддерживаются и AsyncHttpClient (await client.get(...)), и обычные sync/async stream() с теми же proxy и protocol. client.capabilities.http3_proxy и client.capabilities.proxy_carriers сообщают о возможности загруженной сессии; они не гарантируют доступность или пригодность конкретного сервера прокси.
Маршрут использует HTTP/2 поверх TLS к прокси, CONNECT-UDP с reliable capsules RFC9297 и строгий HTTP/3 к конечному HTTPS-серверу. Прокси обязан поддерживать этот способ передачи и канонический URI template. Другие carrier или uri_template отклоняются; HTTPS/3 carrier и datagram-only peer этим API не заявлены. Ответ response.protocol описывает протокол сайта, а не внешнее соединение к прокси.
Для MasqueProxy задайте protocol="h3": фасад выберет h3_only. Расширенный вариант выбора того же режима — application="h3_only" при создании клиента. AUTO и H1/H2 для этого маршрута не предоставляются. Ошибка туннеля не превращается в прямой запрос или понижение протокола. Для обычных HTTP/HTTPS/SOCKS5/SOCKS5H proxy URLs прежняя семантика сохраняется: они не становятся MASQUE и не дают H3-туннель. SOCKS5 UDP ASSOCIATE не реализован.
Hostname и цепочка TLS проверяются для прокси и сайта. Один ca_bundle клиента используется для обеих проверок; отдельных proxy CA настроек нет. Если нужен свой доверенный CA, передайте абсолютный локальный путь к bundle, который подходит обоим узлам. Учётные данные прокси не предназначены для origin. Адрес самого прокси может разрешаться локально; целевое имя передаётся в CONNECT-UDP с сохранением SNI и проверки сертификата конечного сайта.
При фатальном отказе транспорта туннеля текущее действие возвращает исходную ошибку; неисправное соединение исключается из повторного использования. Библиотека не повторяет запрос автоматически. Следующий запрос выполняется только явным вызовом приложения и может открыть новое соединение через тот же прокси; успех зависит от его доступности. Решение повторить операцию принимает приложение с учётом возможных побочных эффектов.
Параметры запроса/stream, cookies, HTTP сроки и HTTP исключения остаются параметрами существующего HTTP API. Неподдержанные сочетания маршрута/протокола/профиля отклоняются; для анализа HTTP-ошибок используйте действующие NativeError и describe_http_error. Отдельного публичного MasqueError нет. Доступ по API-ключу и выбранным подписанным профилям сохраняет обычные правила DuckoNet.
Асинхронный запрос через MASQUE
import asyncio
import os
from duckonet import AsyncHttpClient, MasqueProxy
async def main():
proxy = MasqueProxy(
os.environ["MASQUE_ENDPOINT"],
username=os.environ.get("MASQUE_USER", ""),
password=os.environ.get("MASQUE_PASSWORD", ""),
)
async with AsyncHttpClient(
api_key=os.environ["DUCKONET_API_KEY"], browser="chrome",
proxy=proxy, protocol="h3",
) as client:
response = await client.get("https://origin.example/data")
response.raise_for_status()
print(response.status_code, response.protocol)
asyncio.run(main())
stream() открывается через with client.stream("GET", url, ...) или async with client.stream("GET", url, ...); содержимое читается через stream.iter_bytes() или stream.iter_bytes() в async for соответственно. Маршрут, проверка TLS и строгий H3 действуют и для потока.
Все параметры MasqueProxy
| Параметр | Значение по умолчанию и ограничения |
|---|---|
endpoint | Обязателен. HTTPS URL с hostname; порт 443 по умолчанию, явный порт 1…65535. Только пустой путь или /, без query, fragment, пробелов, обратной косой черты и credentials в URL. Длина нормализованного endpoint — до 4096 UTF-8 байт. |
username | "". Строка до 1024 UTF-8 байт, без управляющих символов. |
password | "". Те же ограничения; передавайте отдельно от URL. |
carrier | "h2". Поддерживается только это значение: HTTP/2 поверх TLS к прокси. |
uri_template | "/.well-known/masque/udp/{target_host}/{target_port}/". Поддерживается только этот шаблон. Хост и порт назначения подставляет транспорт. |
Объект описывает неизменяемый маршрут. Проверка аргументов выполняется при использовании в HTTP API; само создание MasqueProxy(...) не устанавливает соединение и не проверяет сервер. Пустые credentials означают работу без заданного имени и пароля; сервер может требовать авторизацию. Неверные параметры дают ValueError или TypeError; неподдержанный профиль или транспорт может дать NativeError. Отказы прокси, TLS, аутентификации и CONNECT-UDP проверяйте по HTTP диагностике describe_http_error(error). Успешное создание объекта и capability не заменяют успешного запроса.
Профиль, cookies и полный прогон
WebSocket и MASQUE используют существующую установку, каталог state_dir, API-ключ и подписанные профили DuckoNet. Для обновления программы сохраняйте прежний state_dir: в нём находятся идентичность установки и история доверия. Cookies живут в памяти клиента; для сохранения после закрытия приложения используйте export_data() и import_data(). Выбор доступных версий браузера по ключу остаётся общим для HTTP и WS.
Когда используется version="last", клиент подготавливает новые доступные поколения в фоне. Обрамляйте весь связанный прогон client.run(): HTTP, открываемые в нём WS и потоки используют закреплённое поколение до завершения своих операций. Следующий внешний run() выбирает уже подготовленное поколение; вложенный run() наследует текущий. Обновление не переключает профиль внутри открытого WS. При явно выбранной версии профиль остаётся закреплённым.
import os
from duckonet import HttpClient
with HttpClient(
api_key=os.environ["DUCKONET_API_KEY"], browser="chrome", version="last",
) as client:
with client.run():
response = client.get("https://app.example/session")
response.raise_for_status()
with client.websocket("wss://app.example/events", origin="https://app.example") as ws:
ws.send("start")
print(ws.recv())
print(client.browser_profile)
В асинхронном коде используются async with client.run() и async with client.websocket(...). Закрывайте WS и потоки внутри своего run(); сам выход из run() не является командой немедленно прервать уже открытое соединение. Без явного run() автоматические операции используют исходное поколение клиента. client.profile_update_status показывает состояние подготовки профиля, а client.browser_profile — фактический профиль выбранного прогона. Поколение не даёт бессрочное право на сетевую работу: действуют обычные проверки срока действия, отзыва и доступа по ключу, в том числе при обмене по WS.
Cookies принадлежат клиенту и сохраняют штатные domain/path/secure/SameSite правила. Они доступны HTTP и WS в пределах того же владельца и переживают штатную смену поколения. Для общего cookie-состояния переиспользуйте один клиент; два независимых клиента не становятся одним cookie jar только из-за одинакового адреса сервера. cookies= — разовое наложение, а постоянными cookies управляйте через client.cookies.
Поддерживаемые маршруты
| Сценарий | Поддержка |
|---|---|
ws:// / wss:// напрямую | HTTP/1.1 Upgrade; текст, binary и permessage-deflate по согласованию. |
| WS через обычный HTTP/HTTPS/SOCKS5/SOCKS5H proxy | С учётом возможностей профиля и транспорта. socks5 использует локальное разрешение цели; socks5h передаёт имя прокси. Для wss сохраняется проверка TLS сайта. |
HTTP/3 к HTTPS-сайту через MasqueProxy | Строгий H3 внутри CONNECT-UDP, внешний carrier H2 с reliable capsules. |
| WS поверх H2/H3, WS через MASQUE | Не предоставляются. |
| H3 через обычную HTTP/HTTPS/SOCKS строку | Не предоставляется; SOCKS5 UDP ASSOCIATE отсутствует. |
| MASQUE AUTO, H1/H2 к сайту, внешний H1/H3 carrier или другой URI template | Не предоставляются в этом API. |
Внешнее соединение с прокси и соединение с сайтом имеют разные назначения. response.protocol относится к сайту. Настройки браузерного профиля применяются к соединению с целевым сайтом. TLS-соединение с прокси устанавливается отдельно. Библиотека не гарантирует полное совпадение всех сетевых признаков WebSocket и TLS прокси с браузером.