Сервис редко ломается громко. Чаще он отвечает кодом 500, отдаёт не тот заголовок, теряет одно поле в структурированном теле или возвращает пустую страницу, и администратор садится в терминал, чтобы увидеть ответ своими глазами. Для этой работы на сервере живут два клиента: curl, знакомый каждому, кто хоть раз собирал запрос по флагам, и HTTPie, сделанный для ручной отладки. Первый установлен почти везде, второй ставится одним пакетом, и выбирать между ними раз и навсегда не нужно. Статья разбирает механику обоих на примерах команд и объясняет, чем двоичные ответы мешают дружелюбному клиенту и в каком режиме их забирать.

Два подхода к одному запросу и разные привычки администратора

curl появился в конце девяностых и давно стал частью базовой поставки: он есть в минимальных образах дистрибутивов, в спасательных окружениях и почти в каждом контейнере. Сила клиента в наборе опций: отдельные флаги отвечают за заголовки, тело, шифрование, лимиты, повторы и время ожидания. Вывод по умолчанию сырой: тело ответа печатается как есть, а заголовки нужно запрашивать отдельно ключами -i и -I. Такой аскетизм делает инструмент предсказуемым: любой байт на входе и выходе под контролем.

HTTPie появился в 2012 году и вырос из простого наблюдения: подавляющая часть ручных запросов это метод, адрес и несколько пар ключ-значение. Синтаксис построен вокруг этого: короткая команда http, метод и путь, заголовки как пары "имя:значение", тело как пары "ключ=значение". Ответ по умолчанию разобран и подсвечен цветом, а для повторных обращений есть сессии, которые помнят авторизацию. Клиент написан на Python, ставится пакетом httpie и приносит две команды: http для открытых соединений и https для шифрованных. Если префикс протокола в адресе не указан, оба клиента подставляют его сами, поэтому в примерах ниже адреса записаны коротко, без схемы.

Один и тот же запрос на двух клиентах выглядит так:

curl -sS -H "Accept: application/json" api.local/v1/users
http GET api.local/v1/users

Обе строки спрашивают у локального интерфейса приложения список пользователей. Разница не в результате, а в усилии: первая требует помнить ключ для заголовка Accept, вторая собирает запрос из трёх слов и сразу показывает разобранное тело. К 2026 году оба инструмента остаются рабочими, и у каждого своя зона задач, размеченная ниже по сценариям.

Синтаксис HTTPie сокращает набор параметров при ручной отладке

Грамматика запроса умещается в несколько правил. Заголовок передаётся парой "Имя:значение", например "X-Request-Id:8f14e45f". Параметр строки запроса пишется с двойным знаком равенства: "page==2". Поле структурированного тела пишется с одинарным: "name=Ivan". Числа и логические значения передаются формой "имя:=значение". Файл присоединяется знаком @. Примеры ниже покрывают почти всю ручную работу с интерфейсом:

http GET api.local/v1/orders page==2 limit==50
http POST api.local/v1/users name=Ivan role=admin age:=29
http POST api.local/v1/settings debug:=true retries:=5
http --form POST api.local/v1/login login=ivan password="s3cr3t-pw"
http --multipart POST api.local/v1/attachments Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. label="monthly"

Для POST с парами клиент сам собирает структурированное тело и ставит заголовок Content-Type: application/json. Для формы есть ключ --form, для отправки файлов частями есть --multipart. Авторизация передаётся ключом -a, а ключ --auth-type включает схему входа с предварительным вызовом со стороны сервера:

http -a ivan:s3cr3t GET api.local/v1/profile
http --auth-type=digest -a ivan:s3cr3t GET api.local/v1/protected

Тот же POST на curl требует собрать тело строкой и не забыть про Content-Type:

curl -sS -H "Content-Type: application/json" -d '{"name":"Ivan","role":"admin","age":29}' api.local/v1/users

Забытая кавычка или лишняя запятая ломает запрос до отправки, а поиск опечатки в длинной строке отнимает больше времени, чем отладка. Любопытная деталь: с телом curl сам переключается на POST, поэтому ключ -X POST в большинстве команд избыточен.

Цветной вывод и автоматический разбор структурированных тел

Ответ в HTTPie разделён на смысловые зоны: статусная строка и заголовки в верхней части, тело ниже. Структурированные тела разбираются и выравниваются отступами, ключи подсвечиваются отдельным цветом, и нужное поле находится за секунды. Разница между кодом 200 и кодом 401 бросается в глаза до чтения самого тела, а для демонстрации коллеге цветной разбор нагляднее необработанной строки. Для точечного вывода есть узкие ключи: -h печатает только заголовки ответа, -b только тело, -v показывает весь обмен целиком вместе с исходящими заголовками. Ключ --print собирает вывод из отдельных букв, и конструкция --print=H показывает только заголовки запроса, который клиент собрал из ваших пар:

http -h api.local/v1/users
http --print=H api.local/v1/users
http --check-status --print=b api.local/v1/health

Ключ --check-status меняет поведение в скриптах: на коды 4xx и 5xx клиент отвечает ненулевым кодом возврата, тогда как без него любой ответ считается успехом. У curl та же механика спрятана во флаге -f, который подавляет тело при ошибке и возвращает код 22. Разница в умолчаниях обманчива: curl без -f выходит с нулём даже на код 500, и это классическая ловушка в мониторинговых скриптах, где проверка кода возврата засчитывает аварию за успех.

При перенаправлении вывода в канал HTTPie сам отключает цвет, а полный отказ от украшений даёт ключ --pretty=none. Связка с разборщиком jq выглядит так:

http -b --pretty=none api.local/v1/users | jq ".[0].id"
curl -sS api.local/v1/users | jq ".[0].id"

Оба конвейера рабочие, но первый короче в наборе.

Сессии HTTPie сохраняют авторизацию между запросами

Настоящая экономия времени начинается не в одном запросе, а в цепочке. Отладка защищённого интерфейса обычно выглядит как последовательность: вход, проверка профиля, пара пробных операций, и в каждой точке нужен токен или куки. Именованные сессии HTTPie закрывают вопрос одним словом:

http --session=dev -a ivan:s3cr3t GET api.local/v1/login
http --session=dev GET api.local/v1/orders
http --session=dev POST api.local/v1/orders item=screwdriver qty:=3

После первого запроса клиент складывает в файл сессии заголовки авторизации и выданные сервером куки, и следующие запросы несут их автоматически. Файл лежит в подкаталоге sessions каталога конфигурации пользователя, у большинства установок это ~/.config/httpie/sessions/api.local/dev.json. Внутри обычный JSON, файл можно открыть и поправить руками. Ключ --session-read-only=dev берёт сохранённое состояние, но не обновляет его по итогам ответа, а запись --session=/tmp/api-debug.json задаёт путь к файлу сессии явно.

У curl механика работы с куками похожа, но уже: ключ -c сохраняет полученное в файл, ключ -b читает оттуда и отправляет с запросом.

curl -sS -c /tmp/cookies.txt -b /tmp/cookies.txt -u ivan:s3cr3t api.local/v1/login
curl -sS -b /tmp/cookies.txt api.local/v1/orders

Заголовок Authorization curl между запусками не хранит, поэтому токен держат в переменной окружения и подставляют в каждом вызове:

TOKEN="eyJhbGciOiJIUzI1NiJ9..."
curl -sS -H "Authorization: Bearer $TOKEN" api.local/v1/orders

Для повторяющихся общих заголовков у curl есть файл ~/.curlrc: содержимое применяется к каждому запуску, но секретное туда лучше не складывать.

curl отвечает за точный контроль и вывод без прикрас

Там, где нужны измерения и повторяемость, curl раскрывается полностью. Ключ -w печатает метрики ответа по переменным: код состояния, полное время, размер тела:

curl -sS -o /dev/null -w "%{http_code} %{time_total} %{size_download}" api.local/v1/health

В скриптах мониторинга такая строка превращается в готовую точку отчёта без сторонних утилит. Ключ -D с дефисом выгружает заголовки в стандартный поток, а -o /dev/null выбрасывает тело:

curl -sS -D - -o /dev/null api.local/v1/health

Ключ --resolve прибивает имя к адресу на время одного запроса и позволяет проверить конкретный узел за балансировщиком, не трогая /etc/hosts и DNS:

curl -sS --resolve host.local:80:127.0.0.1 host.local/v1/health

Для точной отправки файла есть --data-binary, который передаёт байты без изменений, тогда как -d вырезает из файла переводы строк - мелочь, объясняющая расхождение контрольных сумм при неаккуратной отправке:

curl -sS --data-binary @payload.json -H "Content-Type: application/json" api.local/v1/import

Повторы и докачка решаются ключами --retry и -C с дефисом:

curl -sS --retry 5 --retry-delay 2 -C - -o backup.tar.gz api.local/files/backup.tar.gz

Отдельная сила curl в широте протоколов: клиент говорит на ftp, sftp, scp, ldap и file и закрывает задачи, для которых цветной клиент не предназначен. В спасательных окружениях и минимальных образах curl либо присутствует, либо ставится без лишних зависимостей. Для шифрованного соединения в адресе curl указывают префикс протокола, команда https у второго клиента делает это сама, а проверку внутреннего сертификата задают ключом --verify:

https --verify=/etc/pki/internal-ca.pem GET api.local/v1/health

Двоичный ответ ломает отображение в HTTPie и требует режима сохранения

Дружелюбие HTTPie имеет цену. По умолчанию клиент читает тело ответа как текст, пробует найти в нём структуру и подсветить её цветом. Что меняется, когда тело приходит не текстом? Архив, картинка или сериализованные двоичные данные превращают экран в хаос: нечитаемые знаки, сбитая кодировка, управляющие последовательности, а при большом файле в буфер терминала улетают тысячи строк мусора.

Типичный сценарий: маршрут отдаёт не документ, а файл, и администратор по привычке зовёт цветной клиент. Лечение одно - не показывать тело, а сохранять его. Для этого есть режим --download:

http --download api.local/files/report.tar.gz

Клиент рисует индикатор прогресса, берёт имя файла из заголовка Content-Disposition и печатает путь, по которому сохранил результат. Явное имя задаётся ключом -o:

http --download -o monthly.tar.gz api.local/files/report.tar.gz

Проверить, что маршрут вообще отдаёт файл, можно запросом HEAD, не забирая тело:

curl -sS -I api.local/files/report.tar.gz
http HEAD api.local/files/report.tar.gz

Не каждый маршрут поддерживает HEAD: часть отвечает кодом 405, и тогда надёжнее запросить одни заголовки обычным вызовом, у HTTPie это ключ -h. curl в этой истории вообще не пытается ничего декодировать: байты идут в стандартный поток без вопросов. Отсюда простое правило для конвейеров с двоичными потоками:

curl -sS -o photo.png api.local/images/photo.png
curl -sS api.local/images/photo.png | sha256sum
curl -sS api.local/files/blob.bin | wc -c

Второй клиент в такой цепочке попытается разобрать поток как текст и задержит вывод, собирая весь ответ целиком, а ключ --stream рассчитан на текстовые потоки, а не на произвольные байты. Правило простое: структурированные документы - на экран, двоичные файлы - в файл и через curl.

Практичные правила выбора клиента для ежедневной работы

Сухой остаток собирается в короткую памятку:
1. Ручная проверка ответа, подбор параметров и разбор структурированного тела идут быстрее в HTTPie за счёт короткого синтаксиса и цветного вывода;
2. Регулярная операция в скрипте или задании планировщика пишется на curl, где вывод и код возврата предсказуемы от запуска к запуску;
3. Скачивание файла с индикатором прогресса и именем из заголовка выполняет HTTPie в режиме --download;
4. Конвейер с двоичным потоком, контрольная сумма на лету и докачка после обрыва соединения остаются за curl;
5. Разовая отладка защищённого интерфейса с длинной авторизацией ведётся в именованной сессии HTTPie, пока токен не истёк;
6. Минимальная система, восстановление после сбоя и экзотический протокол остаются за curl, потому что он уже установлен и говорит на большем числе протоколов.

Инструменты не делят одну нишу: curl стоит в системе, потому что он уже есть, HTTPie - потому что с ним быстрее решать задачи ручной отладки. Часть администраторов ставит короткий псевдоним для цветного клиента и держит curl для скриптов и конвейеров. Ошибкой остаётся обратное: забирать архив в режиме разбора тела и потом приводить терминал в порядок, или вручную собирать длинные строки запросов там, где три коротких слова решают всё. Выбор между клиентами решается задачей, а не вкусом.