# Запросы и примеры

Примеры рассчитаны на DuckoNet 0.11.5. Сначала выполните [установку](install.html). Адреса `example.com/api/...` обозначают API вашего приложения: замените их реальными адресами, возвращающими ожидаемый ответ.

<a id="entry"></a>
## GET и параметры URL

Откройте `HttpClient`, передав API-ключ и название браузера. Библиотека сама подготовит установку и сессию. Список пар в `params` позволяет передать один параметр несколько раз.

Клиент принимает абсолютные адреса `http://` и `https://`. Он не заменяет HTTP на HTTPS. Заголовки `User-Agent`, `Accept`, `Accept-Language` и `Accept-Encoding` берутся из профиля, если вы не задали допустимые собственные значения. Защищённые Client Hints добавляются только для HTTPS.

`HttpClient` предназначен для запросов приложения. Он не выполняет браузерный CORS/preflight; для такого сценария нужен отдельный браузерный API. Запросы через `Client` и `AsyncClient` с явным браузерным контекстом описаны [отдельно](legacy.html).

```python
import os
from duckonet import HttpClient

with HttpClient(api_key=os.environ['DUCKONET_API_KEY'], browser='chrome') as client:
    response = client.get('https://example.com/api/items', params=[('tag', 'a'), ('tag', 'b')])
    response.raise_for_status()
    print(response.status_code, response.text)
```

В первом примере `.text` возвращает текст ответа. Метод `.json()` применяйте к адресу, который действительно возвращает JSON.

<a id="ownership"></a>
## Время жизни клиента

Запросы, cookies и `capabilities` доступны после входа в `with` или `async with`. Один экземпляр клиента можно открыть только один раз. После закрытия создайте новый экземпляр. `close()` и `await close()` освобождают ресурсы клиента; переданные вами файлы и цикл событий остаются под управлением приложения.

Все маршруты одного клиента используют общее хранилище cookies. Разные клиенты изолированы. Каталог `state_dir` хранит служебное состояние установки, но не сохраняет cookies автоматически.

<a id="async"></a>
## Асинхронный запрос
```python
import asyncio
import os
from duckonet import AsyncHttpClient

async def main():
    async with AsyncHttpClient(api_key=os.environ['DUCKONET_API_KEY'], browser='chrome') as client:
        response = await client.post('https://example.com/api/items', json={'title': 'Пример'})
        response.raise_for_status()
        print(response.content)

asyncio.run(main())
```

Методы запросов `AsyncHttpClient` вызываются с `await`, а поток открывается через `async with`. Отмена задачи `asyncio` отменяет запрос и освобождает его ресурсы. При отмене дождитесь выхода из `async with`, чтобы освобождение ресурсов завершилось.

<a id="payloads"></a>
## JSON, формы и файлы

В следующих фрагментах `client` — открытый `HttpClient`. Поместите нужные вызовы внутрь `with HttpClient(...) as client:` из первого примера. Каждый вызов ниже показывает отдельный способ отправки тела.
```python
# Ниже client уже открыт через with.
response = client.post('https://example.com/api/json', json={'name': 'Тест', 'enabled': True})
response = client.post('https://example.com/api/form', data=[('tag', 'a'), ('tag', 'b')])
response = client.post('https://example.com/api/text', data='Привет', encoding='utf-8')
response = client.post('https://example.com/api/bytes', body=b'\x00\x01')
with open('report.txt', 'rb') as source:
    response = client.post('https://example.com/api/upload', data={'title': 'Отчёт'},
                           files={'file': ('report.txt', source, 'text/plain')})
```

Перед отправкой тело целиком помещается в память. Максимум — 16 MiB, включая служебные разделители multipart. Файл читается с текущей позиции; библиотека его не закрывает. Можно явно указать имя файла, иначе из `file.name` берётся только имя без пути. Для повторяющихся полей передайте `files` как список пар. Несовместимые сочетания `body`, `data`, `json` и `files` отклоняются до отправки.

<a id="cookies-example"></a>
## Добавление cookies
```python
client.cookies.set('theme', 'dark', url='https://example.com/', secure=True, same_site='lax')
value = client.cookies.get('theme', domain='example.com', path='/')
response = client.get('https://example.com/api/items', cookies={'theme': 'light'})
# Значение theme для одного запроса не изменяет хранилище.
client.cookies.delete('theme', domain='example.com', path='/', host_only=True)
```

`cookies_enabled=False` отключает отправку cookies для этого запроса. С ним нельзя передать непустой `cookies` или заголовок `Cookie`. Параметр `persist_cookies=False` действует отдельно: запрещает сохранять полученные `Set-Cookie`, но не запрещает отправку. Подробнее — в разделе [Cookies](cookies.html).

<a id="stream-example"></a>
## Скачивание по частям
```python
with client.stream('GET', 'https://example.com/download', timeout_seconds=90) as stream:
    print(stream.status_code, stream.headers.get('Content-Type'))
    with open('download.bin', 'wb') as target:
        for chunk in stream.iter_bytes(65536):
            target.write(chunk)
    final_response = stream.result()

with open('download.bin', 'wb') as target:
    final_response = client.consume('GET', 'https://example.com/download', target.write)
```

Поток становится доступен после получения заголовков конечного ответа с учётом перенаправлений. Поток не накапливает тело в `response.content`: сохраняйте получаемые части сами. После чтения до конца `result()` возвращает итог запроса, но не собирает прочитанные части заново. Выход из контекста закрывает или отменяет поток. Читать его может один потребитель. Размер части — от 1 байта до 16 MiB, по умолчанию 65536 байт.

```python
# Выполните этот фрагмент внутри async-функции с открытым AsyncHttpClient.
async with client.stream('GET', 'https://example.com/download') as stream:
    async for chunk in stream.iter_bytes():
        process_chunk(chunk)  # Обработчик bytes вашего приложения.
    final_response = await stream.result()
```

Для явной отмены асинхронного потока вызовите `await stream.cancel()`. У синхронного потока `cancel()` вызывает отмену базового запроса. После отмены всё равно завершите контекст. Асинхронный `consume()` ожидает результат обработчика, если тот вернул объект, поддерживающий `await`.

<a id="overrides-example"></a>
## Прокси и параметры запроса
```python
from duckonet import ProxyRoute

response = client.get('https://example.com/api/items',
    headers=[('Accept', 'application/json'), ('X-Trace', 'demo')],
    proxy=ProxyRoute('socks5h://127.0.0.1:1080'), protocol='h2',
    timeout_seconds=60, body_limit=10 * 1024 * 1024,
    allow_redirects=False, diagnostics=True)
print(response.protocol, response.timings)
```

Для этого примера нужен работающий SOCKS5-прокси на `127.0.0.1:1080`; замените адрес на свой. Если прокси недоступен, клиент не переключается на прямое соединение. `proxy=None` явно выбирает прямое соединение. Сочетания `h3` или `auto` с прокси отклоняются до выхода в сеть. При `diagnostics=True` доступны измеренные данные, которые удалось получить для этого запроса.

<a id="errors-example"></a>
## Обработка ошибок
```python
from duckonet import HTTPStatusError, HttpTimeoutError, NativeError, describe_http_error

try:
    response = client.get('https://example.com/api/items', timeout_seconds=15)
    response.raise_for_status()
except HTTPStatusError as error:
    status = error.status_code
except (HttpTimeoutError, NativeError) as error:
    details = describe_http_error(error)
    print(details.group, details.code, details.partial_response_available)
```

Пример выводит категорию и код ошибки без текста, который может содержать данные запроса. Ошибки аргументов `ValueError` и `TypeError`, ошибки запуска, декодирования, JSON и отмена `asyncio` обрабатываются отдельно. `describe_http_error()` описывает исходное исключение, не заменяя его класс. Ответ HTTP 4xx или 5xx сам по себе не вызывает `NativeError`; для проверки вызовите `raise_for_status()`.
