DuckoNetДокументация 0.11.5Документация DuckoLab
Разделы

Прокси, потоки, диагностика и ошибки

Здесь описаны настройки для случаев, когда стандартного запроса недостаточно. Возможность конкретного сочетания зависит от выбранного профиля и транспорта; её проверяет открытая сессия.

Маршруты

В HttpClient и AsyncHttpClient аргумент proxy=None выбирает прямое соединение. Для прокси передайте URL или ProxyRoute. Если аргумент запроса пропущен, сохраняется настройка клиента. У Client и AsyncClient прокси задаётся только объектом ProxyRoute в конструкторе. Поддерживаемые схемы: http, https, socks5, socks5h, если их разрешают профиль и транспорт.

В URL допускаются закодированные логин и пароль. У ProxyRoute(endpoint, username='', password='') они передаются отдельно; endpoint не должен их содержать. repr() скрывает учётные данные. Путь должен быть пустым или /; query и fragment запрещены. Порты по умолчанию: 80, 443, 1080, 1080 соответственно; допустимый диапазон — 1–65535. Имя хоста прокси нормализуется через IDNA, адрес IPv6 заключается в скобки. Логин и пароль — строки до 1024 байт UTF-8 без управляющих символов. Функция duckonet.http_client306.normalize_proxy(proxy) возвращает нормализованный ProxyRoute или None.

socks5 разрешает адрес целевого сайта локально, socks5h — через прокси. Для HTTPS-прокси требуется поддержка транспорта; его TLS-соединение проверяется отдельно от соединения с целевым сайтом. Неподдерживаемое сочетание вызывает ошибку. Если прокси не сработал, прямого подключения вместо него не будет. Для обычных URL-прокси h3 и auto не поддерживаются. В 0.11.5 для MasqueProxy используется protocol='h3' (строгий H3), см. MASQUE. Переменные окружения с настройками прокси автоматически не используются.

protocol принимает h1, h2, h3 или auto. Режимы h3 и auto выбирают смешанный профиль: первое соединение может использовать HTTP/2, а затем перейти на HTTP/3 по Alt-Svc и доступности маршрута. Они не означают «только QUIC». Фактический протокол указан в response.protocol: 1, 2 или 3.

Сочетание варианта профиля и нормализованного прокси, включая учётные данные, определяет маршрут. Значения h3 и auto у protocol выбирают один и тот же вариант, поэтому используют один маршрут. Клиент переиспользует его сессию. За время жизни клиента допускается до 8 маршрутов, включая начальный; превышение вызывает ValueError. Активная сессия не изменяется и не вытесняется. Маршруты используют общее ядро и cookies. Версию профиля, profile_id и доверенные CA нельзя переключить параметрами отдельного запроса.

HttpCapabilities и платформы

Получите client.capabilities после открытия клиента. Низкоуровневый эквивалент — duckonet.http_native306.capabilities(session). Неизменяемая модель HttpCapabilities описывает открытую сессию:

ПолеЗначение
ordinary_http, cookie_management, shared_cookie_routesbool — поддержка обычного HTTP, управления cookies и общих cookies маршрутов
diagnostics, preserve_urlbool — поддержка выбранной сессией
protocolstuple[str] — доступные протоколы
conditional_proxy_schemestuple[str] — обычные схемы прокси; допустимость сочетания проверяется отдельно
websocket, http3_proxybool — наличие соответствующей возможности загруженного runtime
proxy_carrierstuple[str] — MASQUE carriers; сейчас h2, если доступен HTTP3 proxy
unsupportedstreaming_upload, unbounded_body; http3_proxy добавляется, если runtime не имеет этой возможности
timeout_max_seconds, redirects_max300 и 100
request_max_bytes16777216
response_default_bytes, response_max_bytes1048576 и 67108864
immutable_routes_max8

Создание модели вручную не проверяет транспорт. Используйте значение, полученное у клиента.

Выпуск предназначен для CPython 3.12 x64. Для Windows действуют требования к системным компонентам, для Linux — Ubuntu 24.04 x64. Windows 11 является системой с подтверждённой совместимостью, а не жёстким ограничением версии ОС в библиотеке. Профили выбираются из подписанного каталога с учётом прав. Firefox, Safari, macOS и ARM этим пакетом не поддерживаются.

HttpStream и AsyncHttpStream

HttpStream и AsyncHttpStream возвращаются из client.stream(). Открывайте их через with или async with, читайте тело до конца, затем вызывайте result(). Если нужен только файл или обработчик байтов, начните с примеров скачивания.

history содержит завершённые перенаправления, response — текущий HttpResponse. После открытия доступны код, протокол, URL и заголовки; body и content ещё равны None. Поля ответа делегируются response, остальные методы — базовому Request или AsyncStream. Обращаться к _stream и _native не нужно.

read(size=65536) возвращает байты. Пустой результат b'' означает конец тела без ошибки чтения. Синхронная обёртка ждёт готовности и не возвращает промежуточный None, который возможен у низкоуровневого Request.read(). iter_bytes(size=65536), обычная и асинхронная итерация читают до конца.

result() или await result() возвращает итоговый HttpResponse, не собирая заново прочитанное тело; content может оставаться None. Размер части — от 1 до 16777216 байт, по умолчанию 65536. У потока один читатель. Общий таймаут не начинается заново при следующей части или перенаправлении. await AsyncHttpStream.cancel() закрывает базовый поток; синхронный cancel() вызывает Request.cancel(). Даже после отмены завершите контекст: он отвечает за освобождение ресурсов.

HTTP-запросы через Session и AsyncSession

Session предоставляет compile, request, stream, consume для запросов с заданным контекстом и методы с суффиксом _http для обычных HTTP-запросов с настройками cookies, диагностики и URL. На этом уровне приложение само управляет временем жизни объектов:

Метод SessionРезультат и владение ресурсами
compile_http(method, url, **kwargs)Compiled; вызывающий код обязан вызвать release()
request_http(method, url, **kwargs)Response; созданные Compiled и Request освобождаются автоматически
stream_http(method, url, **kwargs)Request для чтения по частям; вызывающий код закрывает его контекст
consume_http(method, url, consumer, *, chunk_size=65536, **kwargs)Response после передачи тела обработчику

request_http() допускает только body_mode=1. stream_http() сам выбирает body_mode=2 и запрещает явно задавать его. Лимит буфера body_limit у низкоуровневого stream_http() по умолчанию равен 65536 байт; у HttpClient — 1048576.

Параметры compile_http(): headers=() (пары), body=b'', timeout_ms=30000 (целое 1–300000), body_limit=1048576, header_limit=65536, body_mode=1, context=None, cookies=(), cookies_enabled=True, persist_cookies=True, body_present=False, preserve_url=False, diagnostics=False, force_h1=False.

Несмотря на context=None в сигнатуре, для этого пути обязателен BrowserHopContext. Признак body_present должен соответствовать телу, логические флаги — иметь тип bool. force_h1=True принудительно выбирает HTTP/1.1. У HttpClient для этого используйте protocol="h1". В Session.compile() такого аргумента нет.

На этом уровне не обрабатываются params, json, data, files и автоматические перенаправления; результат не оборачивается в Headers или HttpResponse. Cookies проходят проверку нативного ядра. Функция duckonet.http_native306.compile_http(session, method, url, **options) предоставляет тот же вызов.

AsyncSession.request_http(), stream_http() и consume_http() возвращают соответственно Response, AsyncStream и Response. Запрос и consume_http() вызываются с await, поток открывается через async with. Обработчик consume_http() может быть синхронным или возвращать объект для await; асинхронная сессия дождётся его. Публичного AsyncSession.compile_http() нет; отдельная компиляция доступна у Session.

Session.cookies и AsyncSession.cookies доступны после входа. share_cookies_from(source) подключает хранилище исходной сессии к ещё не использованной целевой сессии того же ядра и модуля. После компиляции или между разными ядрами операция запрещена. Для асинхронного варианта обе сессии должны быть открыты; сам метод остаётся синхронным.

Ожидание заголовков

Request.wait_headers(timeout=None) и await AsyncStream.wait_headers(timeout=None) возвращают HeaderSnapshot окончательных заголовков без чтения тела. При истечении ожидания возвращается None; запрос при этом не отменяется. timeout — конечное неотрицательное число секунд; None означает ожидание готовности или завершения запроса.

HeaderSnapshot содержит request_id, owner_id, epoch (int) и blocks (кортеж ResponseBlock). Финальный блок имеет section=2, целочисленные status_code и protocol, заголовки — пары bytes. Раннее завершение с ошибкой сохраняет исходное исключение и частичный ответ.

Во время ожидания заголовков нельзя одновременно читать тот же поток. Истечение timeout оставляет запрос активным; отмена асинхронной задачи ожидания, напротив, отменяет запрос и освобождает его ресурсы.

Сигнатура compile_http

Сигнатура низкоуровневого вызова (Session.compile_http принимает те же именованные аргументы):

# Справочная сигнатура; не сетевой вызов.
def compile_http(session, method, url, *, headers=(), body=b'', timeout_ms=30000,
                 body_limit=1048576, header_limit=65536, body_mode=1, context=None,
                 cookies=(), cookies_enabled=True, persist_cookies=True,
                 body_present=False, preserve_url=False, diagnostics=False, force_h1=False):
    ...

force_h1 относится к низкоуровневому вызову. У HttpClient.request() и в словаре advanced такого параметра нет: используйте protocol='h1'. Классы Cookie и Cookies импортируются из duckonet; управление хранилищем описано в разделе Cookies.

Request.headers_snapshot() немедленно возвращает доступный HeaderSnapshot или None, если снимок ещё не готов. Снимок может не содержать окончательных заголовков. Чтобы дождаться их, используйте wait_headers(); окончательный блок имеет section=2.

Конструктор Cookies(session) предназначен для прямой работы с сессией. У клиента готовое хранилище доступно как client.cookies.

Диагностика запроса

Диагностика по умолчанию отключена. При diagnostics=True и поддержке транспорта response.diagnostics содержит status:int, timings:dict, valid_mask:int, not_applicable_mask:int, flags:int, outgoing_headers (кортеж пар строк), headers_redacted=True, header_representation='observed-logical-fields', reused:bool|None. Если наблюдение недоступно, возвращается None; response.timings также может быть None. Для перенаправлений смотрите history[i].diagnostics.

Время указано в секундах от начала передачи. Это накопленные отметки, а не отдельные длительности этапов. Значение None означает, что измерение недоступно.

КлючЧто измеряет
namelookupЗавершение разрешения имени.
connectЗавершение подключения к серверу или прокси.
appconnectЗавершение TLS-рукопожатия, когда оно применимо.
pretransferГотовность к передаче после подготовки соединения.
starttransferНачало получения ответа.
totalВремя передачи до завершения.

Биты 0–5 в valid_mask соответствуют перечисленным ключам. Без нужного бита значение равно None. not_applicable_mask отмечает неприменимые этапы, например при повторном использовании соединения. В flags бит 0 означает, что факт переиспользования известен, бит 1 — что соединение переиспользовано. Без бита 0 reused=None. Отсутствующее наблюдение нельзя подменять нулём или настройкой клиента.

outgoing_headers содержит наблюдаемые логические заголовки с замаскированными секретами. Это не текст HTTP/1 и не точная упаковка кадров HTTP/2 или HTTP/3. Если status!=0, поля могут быть пусты; настроенные заголовки не подставляются вместо отсутствующего наблюдения. Публичный режим выдачи незамаскированных данных не предусмотрен.

Для низкоуровневого ответа существует duckonet.http_native306.copy_diagnostics(module, response): преобразует диагностику в словарь или возвращает None, если наблюдение недоступно. При работе через HttpClient используйте response.diagnostics.

Ошибки

Сначала различайте ответ сайта и сбой выполнения. HTTP 4xx/5xx — ответ сервера; raise_for_status() превращает его в HTTPStatusError. Неверные аргументы, невозможность подключиться и ошибка запуска клиента — другие исключения.

После определения категории:

Не повторяйте автоматически запрос с изменением данных, если результат неизвестен: сервер мог успеть его выполнить.

describe_http_error(error:BaseException) возвращает неизменяемый HttpErrorDetails: group:str, code:int|str|None, domain:int|None, native_code:int|None, phase:int|None, partial_response_available:bool. Последние числовые поля по умолчанию равны None, признак частичного ответа — False. Исходное исключение и способ его перехвата не меняются; текст ошибки не копируется. Для NativeError сохраняются его коды, а сам частичный ответ остаётся в error.partial.

Категории ошибок:

groupУсловие
cancelCancelledError или нативный код −5
timeoutTimeoutError или −6
validationValueError, TypeError или −1
capability−2: неподдерживаемая возможность
http-policy−17, −18 или HTTP_REDIRECT_*
limit−11: технический предел
dns, tls, http, lifecycleСоответствующая нативная область ошибки
proxyТолько domain=9 с native_code=5 или 97
transportОбщая ошибка транспорта
http-statusHTTPStatusError
startup, licenseStartupError/CatalogError; license для кода с LICENSE, ENTITLEMENT, PERMISSION или API_KEY
unknownОстальные случаи

HttpRedirectError(RuntimeError) создаётся с (code, response) и сохраняет эти поля. HttpTimeoutError(TimeoutError) принимает (message, partial=None), содержит код HTTP_TOTAL_TIMEOUT, поля partial и partial_response_available. Частичный результат может быть последним доступным HttpResponse.

HTTPStatusError(RuntimeError) принимает response и сохраняет его вместе со status_code; возникает при raise_for_status(). NativeError сохраняет request_id, owner_id, epoch, operation и partial. Проверяйте outcome и body_complete: частичный ответ не равен успешно завершённому. Ошибки Unicode и JSON не являются ошибками транспорта.

Явный контекст запроса

Пример задаёт контекст cookies самостоятельно. Здесь client — уже открытый HttpClient:

from dataclasses import replace
from duckonet import BrowserHopContext, CookieContext

url = 'https://example.com/api/items'
base = client.distribution.navigation_context(url)
browser = replace(base, cookies=CookieContext(url, url, kind='fetch', credentials='include'),
                  document_url=url, initial_navigation=False, user_activation=False, storage_access=False)
hop = BrowserHopContext(browser=browser, mode='cors', body_present=False)
response = client.get(url, context=hop, advanced={'timeout_ms': 45000})

В примере client уже открыт. Явный контекст запроса сам по себе не запускает браузерный Fetch. Не передавайте одновременно advanced.timeout_ms и timeout_seconds в одном вызове.

Распаковка ответа

Тело gzip-ответа распаковывается при получении. В response.content и частях потока находятся уже распакованные байты; повторно вызывать gzip.decompress() не нужно. Заголовки могут сохранять Content-Encoding и исходный Content-Length, поэтому размер content может отличаться от заголовка.

При чтении целого ответа body_limit ограничивает распакованное тело. При потоковом чтении этот параметр ограничивает буфер, а не общий размер скачивания.

Отмена запроса

Вход в with client.stream(...) завершается после получения заголовков конечного ответа. До этого отдельного объекта потока для отмены нет. Синхронную операцию можно прервать из другого потока вызовом client.close(): он закрывает весь клиент и отменяет все его активные запросы. После этого создайте новый экземпляр клиента.

В асинхронном коде отменяйте задачу запроса через task.cancel(). У уже открытого потока можно вызвать await stream.cancel(). В обоих случаях дождитесь выхода из контекста. Блокирующий пользовательский file.read() или обработчик consume() нельзя прервать посередине выполнения.

Конструкторы потоков

Для интеграции с низкоуровневым запросом классы HttpStream и AsyncHttpStream доступны в duckonet.http_client306. Сигнатура: (stream, encoding=None, *, history=(), deadline=None, response=None). deadline — абсолютное значение монотонных часов в секундах, а не длительность ожидания. При обычном использовании client.stream() создаёт эти объекты самостоятельно; вручную задавать deadline не нужно.

Текст страницы