# Запросы, ответы и авторизация

## Быстрый запрос

```python
import os
from duckonet import get

response = get(
    'https://example.com/',
    api_key=os.environ['DUCKONET_API_KEY'],
    browser='chrome',
    version='last',
)
print(response.status_code)
print(response.text)
```

`request(method, url, *, api_key, browser='chrome', version=None, state_dir=None, client_options=None, **kwargs)` открывает временный `HttpClient`, выполняет один запрос и закрывает клиент. Ответ остаётся доступен после закрытия. Параметры самого клиента, например прокси и ограничения, передаются словарём `client_options`; параметры запроса — именованными аргументами. `api_key` и `browser` нельзя дублировать в `client_options`. Явные `version` и `state_dir` также не должны дублироваться там.

Сокращения `get`, `head`, `post`, `put`, `patch`, `delete`, `options` принимают `url` и те же именованные аргументы. Для асинхронного кода доступны `async_request`, `async_get`, `async_head`, `async_post`, `async_put`, `async_patch`, `async_delete`, `async_options`; их результат нужно ожидать через `await`.

Эти функции возвращают буферизованный ответ. Потоковое чтение требует явно открытого клиента и `client.stream(...)`. Для серии запросов также держите один клиент открытым, чтобы переиспользовать его состояние и соединения.

## Настройки HTTP-клиента

Пример с общим адресом и параметрами:

```python
import os
from duckonet import HttpClient

with HttpClient(
    api_key=os.environ['DUCKONET_API_KEY'],
    browser='chrome',
    version='last',
    base_url='https://example.com/api/',
    params={'language': 'ru'},
    raise_for_status=True,
) as client:
    response = client.get('items', params={'page': 2})
    print(response.json())
```

`AsyncHttpClient` использует те же настройки; открывайте его через `async with`, запросы выполняйте через `await`.

| Параметр конструктора | Значение по умолчанию | Назначение |
|---|---|---|
| `base_url` | `None` | Базовый абсолютный HTTP(S)-адрес. Относительные адреса разрешаются по обычным правилам URL; абсолютный адрес задаёт собственный сервер. Логин и пароль внутри URL запрещены. |
| `params` | `None` | Общие query-параметры, словарь или последовательность пар. Параметр запроса с тем же именем заменяет общий; повторяющиеся имена внутри запроса сохраняются. |
| `auth` | `None` | `BasicAuth` или `BearerAuth`. Общая авторизация привязана к origin базового адреса либо первого запроса, если базовый адрес не задан. |
| `raise_for_status` | `False` | Автоматически вызывать `response.raise_for_status()` для конечного ответа. |
| `retry_policy` | `None` | Необязательная политика повторов; без неё повторов нет. |
| `response_cache` | `None` | Необязательный кеш HTTP-ответов. Это отдельный объект, которым владеет вызывающий код. |
| `cache_namespace` | `'default'` | Дополнительная область кеша: 1–64 символа из букв, цифр, `_`, `-`, `.`. |
| `network` | `None` | Неизменяемый `NetworkOptions` для соединений этого клиента. |
| `tls` | `None` | `TLSOptions`: клиентский сертификат и дополнительные SPKI-пины. |
| `rate_limit` | `None` | `RateLimit`: ограничение скорости передачи тела. |
| `diagnostic_hook` | `None` | Функция, получающая описание завершённого запроса без заголовков и тела. |

Настройки соединения `network`, `tls`, `rate_limit` также принимают `Client`, `AsyncClient`, `Distribution.session` и `Distribution.async_session`. При прямом создании транспорта они входят в `TransportConfig`. Они задаются для сессии, а не меняются посреди уже работающего соединения.

Дополнительные параметры `client.request` и сокращений `get`/`post`/других методов:

| Параметр | Если не передан | Поведение |
|---|---|---|
| `auth` | Настройка клиента | Явный `None` отключает общий helper на этом запросе. Явный helper задаёт авторизацию для выбранного запроса; обычные ограничения перенаправлений сохраняются. |
| `referer` | Заголовок не добавляется helper-ом | Абсолютный HTTP(S)-адрес для `Referer`. Конфликт с одноимённым явным заголовком вызывает ошибку. |
| `accept_encoding` | Действуют настройки профиля | Список через запятую из `identity`, `gzip`, `deflate`, `br`, `zstd`. Неподдержанное имя или конфликт с явным заголовком отклоняется. |
| `use_default_params` | `True` | `False` исключает общие `params` клиента из данного запроса. `params=None` или пустая последовательность сами по себе общие параметры не очищают. |
| `raise_for_status` | Настройка клиента | `True` включает автоматическую ошибку HTTP-статуса, `False` отключает. |
| `retry_policy` | Настройка клиента | Явный `None` отключает повторы данного запроса. |
| `cache` | Включён, если задан backend | `False` обходит кеш; `True` требует настроенный `response_cache`. Потоковые запросы и запросы с телом кеш не используют. |

`BasicAuth(username, password, encoding='latin-1')` формирует HTTP Basic. Для UTF-8 укажите `encoding='utf-8'`; двоеточие в имени пользователя и управляющие символы запрещены. `BearerAuth(token)` формирует Bearer-заголовок. Эти объекты не показывают свои секреты в `repr`.

Helper авторизации нельзя совмещать с явным `Authorization` в том же запросе. Авторизацию другого сервера задавайте отдельным клиентом либо явным параметром запроса. API-ключ DuckoNet предназначен для доступа к библиотеке; он не является Bearer-токеном вашего целевого сайта.

У обоих helper-объектов `authorization()` возвращает готовое секретное значение заголовка. Обычно вызывать метод вручную не требуется; не выводите его результат в журнал.

Остальные правила запросов сохраняются: `json=None` отправляет JSON `null`; `body` содержит `bytes`, `data` — текст, байты или поля формы; для больших тел используйте существующий `upload`. Общий таймаут ограничивает весь запрос, перенаправления и повторы вместе.

## Ответ

| Свойство | Значение |
|---|---|
| `ok` | `True`, если `status_code < 400`. Это не проверка содержимого ответа. |
| `is_redirect` | Статус 301, 302, 303, 307 или 308 и присутствующий `Location`. |
| `url_text` | URL ответа в виде `str`. Исходное свойство `url` сохраняет свой прежний тип. |
| `charset` | Кодировка из `Content-Type`, либо `None`. |
| `status_description` | Стандартное описание числового HTTP-статуса, например `Not Found`; пустая строка для неизвестного кода. Это не фраза, полученная от сервера. |
| `reason` | `None`: серверная reason phrase не предоставляется. |
| `cookies_received` | Кортеж `ReceivedCookie(name, value, attributes, accepted=None)`, разобранных из `Set-Cookie` этого ответа. |
| `request_info` | Необязательный `HttpRequestInfo(method, url, headers)` исходящего логического запроса. Query и fragment исключены; известные заголовки авторизации и cookies замаскированы. |
| `retry_history` | Кортеж описаний сделанных повторов `RetryAttempt`. |
| `attempt_count` | Число сетевых попыток этого запроса; для ответа из кеша — 0. |
| `from_cache` | Получен ли ответ из HTTP-кеша. |
| `cache_info` | `CacheInfo(from_cache, age_seconds, expires_at)` либо `None`; возраст в секундах, срок — Unix-время. |
| `socket_info` | Необязательные фактические локальный и удалённый сетевые адреса. |

`cookies_received` — снимок атрибутов ответа, а не контейнер действующих cookies. `accepted=None` означает, что снимок не содержит вердикта cookie-политики. Действующие cookies продолжает обслуживать `client.cookies`; повторно добавлять весь `Set-Cookie` из снимка не нужно.

`request_info` не является записью сырых пакетов. Пользовательские заголовки и путь URL могут содержать данные приложения: не публикуйте такой снимок без собственного контроля. Для журнала без заголовков и пути используйте `diagnostic_hook`.

Существующие `content`, `text`, `decode`, `json`, `headers`, `history` и `raise_for_status` сохраняют своё назначение. При автоматической ошибке HTTP-статуса ответ находится в `HTTPStatusError.response`.

## Чтение по строкам

```python
import os
from duckonet import HttpClient

with HttpClient(api_key=os.environ['DUCKONET_API_KEY']) as client:
    with client.stream('GET', 'https://example.com/events') as stream:
        for line in stream.iter_lines(encoding='utf-8'):
            print(line)
```

`stream.iter_lines(*, encoding=None, errors='strict', max_line_bytes=1048576, chunk_size=65536)` возвращает строки без разделителей. Поддерживаются `LF`, `CRLF` и `CR`, пустые строки и последняя строка без завершающего переноса. Кодировка выбирается по явному параметру, настройке потока, `charset` ответа, затем UTF-8. Ошибки декодирования обрабатываются согласно `errors`.

`max_line_bytes` — предел кодированного представления одной строки, от 1 до 16777216. `chunk_size` — порция чтения, в том же диапазоне. Превышение предела строки вызывает `ValueError`; неверные байты при `errors='strict'` — ошибку декодирования. Для `AsyncHttpClient` используйте `async with client.stream(...)` и `async for line in stream.aiter_lines(...)`; `iter_lines` у асинхронного потока возвращает тот же асинхронный итератор.

При досрочном выходе закрывайте контекст потока. Если сам итератор строк закрыт до EOF, он отменяет незавершённый запрос. Все тело целиком для разделения строк не накапливается.

## Выбор браузерной версии на один прогон

```python
import os
from duckonet import HttpClient

with HttpClient(api_key=os.environ['DUCKONET_API_KEY'], version='last') as client:
    print(client.available_browser_versions(version_range=(150, 155)))
    with client.run(version_range=(150, 155), random_profile=True):
        print(client.browser_profile)
        response = client.get('https://example.com/')
```

`available_browser_versions(*, version_range=None)` возвращает кортеж доступных версий из подписанного совместимого каталога с учётом прав аккаунта и текущего протокола. Метод вызывается на открытом клиенте. Диапазон — включительно, два целых major-номера; он не создаёт отсутствующие версии.

`run(*, version_range=None, random_profile=False)` фиксирует профиль на полный прогон. Если задан диапазон и `random_profile=False`, выбирается старшая доступная версия диапазона; при `True` — один случайный допустимый профиль. Все запросы, перенаправления и повторы внутри прогона используют этот выбор. Вложенный прогон не может произвольно заменить уже закреплённый профиль. Диапазон несовместим с явным `profile_id`.

Асинхронный вариант использует `async with client.run(...)`; список версий и свойство `browser_profile` читаются обычным вызовом. Cookies остаются в общем контейнере логического клиента. `version='last'` продолжает означать последнюю разрешённую версию, а переход на обновлённый профиль происходит между полными прогонами.
