Прокси, потоки, диагностика и ошибки
Здесь описаны настройки для случаев, когда стандартного запроса недостаточно. Возможность конкретного сочетания зависит от выбранного профиля и транспорта; её проверяет открытая сессия.
Маршруты
В 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_routes | bool — поддержка обычного HTTP, управления cookies и общих cookies маршрутов |
| diagnostics, preserve_url | bool — поддержка выбранной сессией |
| protocols | tuple[str] — доступные протоколы |
| conditional_proxy_schemes | tuple[str] — обычные схемы прокси; допустимость сочетания проверяется отдельно |
| websocket, http3_proxy | bool — наличие соответствующей возможности загруженного runtime |
| proxy_carriers | tuple[str] — MASQUE carriers; сейчас h2, если доступен HTTP3 proxy |
| unsupported | streaming_upload, unbounded_body; http3_proxy добавляется, если runtime не имеет этой возможности |
| timeout_max_seconds, redirects_max | 300 и 100 |
| request_max_bytes | 16777216 |
| response_default_bytes, response_max_bytes | 1048576 и 67108864 |
| immutable_routes_max | 8 |
Создание модели вручную не проверяет транспорт. Используйте значение, полученное у клиента.
Выпуск предназначен для 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. Неверные аргументы, невозможность подключиться и ошибка запуска клиента — другие исключения.
После определения категории:
validation— исправьте аргументы запроса.capability— выберите поддерживаемое сочетание профиля, протокола и прокси.limit— уменьшите данные или увеличьте соответствующий лимит в допустимом диапазоне.dns,tls,proxy— проверьте адрес, доверенные сертификаты или параметры прокси соответственно.http-status— обработайте код и тело ответа целевого сайта.startup,license— проверьте код запуска и права API-ключа.
Не повторяйте автоматически запрос с изменением данных, если результат неизвестен: сервер мог успеть его выполнить.
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 | Условие |
|---|---|
| cancel | CancelledError или нативный код −5 |
| timeout | TimeoutError или −6 |
| validation | ValueError, 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-status | HTTPStatusError |
| startup, license | StartupError/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 не нужно.