Лаборатория кодера

Справочник · Веб и HTTP

curl: запросы из командной строки

GET и POST, JSON и формы, заголовки, авторизация, cookie, загрузка файлов, перенаправления, повторы и замер времени ответа. Всё, что нужно для проверки API без графических клиентов.

Обновлено · 8 мин чтения

curl есть почти везде: на серверах, в контейнерах, в macOS и Windows 10 и новее. Им удобно проверять API, скачивать файлы и выяснять, что именно отвечает сервер. В примерах ниже адрес лежит в переменной: так короче и так же удобно в скриптах.

bash
API=localhost:8080/api

Простые запросы

bash
curl $API/users                       # GET, тело ответа в терминал
curl -i $API/users                    # с заголовками ответа
curl -I $API/users                    # только заголовки (метод HEAD)
curl -s $API/users                    # без индикатора прогресса
curl -sS $API/users                   # тихо, но ошибки показывать
curl -o users.json $API/users         # сохранить в файл
curl -v $API/users                    # подробно: заголовки запроса и ответа, TLS
curl "$API/users?page=2&limit=50"     # параметры в кавычках

Параметры строки запроса можно не склеивать руками: -G вместе с --data-urlencode соберёт и закодирует их сам.

bash
curl -G $API/search --data-urlencode "q=привет мир" --data-urlencode "lang=ru"
# уйдёт GET /api/search?q=%D0%BF%D1%80%D0%B8...&lang=ru

Как устроено процентное кодирование, видно в URL-кодировщике.

Самые нужные ключи

КлючЧто делает
-X METHODметод запроса: POST, PUT, PATCH, DELETE
-H "Имя: значение"добавить заголовок, можно несколько раз
-d 'данные'тело запроса; сам ставит метод POST
-d @file.jsonтело из файла
--json '...'тело JSON с нужными заголовками (curl 7.82 и новее)
-F name=valueполе формы multipart/form-data
-F file=@photo.jpgзагрузка файла
-u user:passBasic-авторизация
-Lидти по перенаправлениям
-o file / -Oсохранить в файл / под именем из адреса
-C -докачать прерванную загрузку
-s / -Sтихий режим / но показывать ошибки
-f или --failпри ответе 4xx и 5xx вернуть ненулевой код выхода
-w '...'вывести сведения после запроса: код, время, размер
-m 10ограничить всю операцию десятью секундами
--connect-timeout 3ограничить только установку соединения
--retry 3повторить при временных ошибках
-b / -cотправить cookie / сохранить полученные cookie в файл
-kне проверять сертификат (только для отладки)
--compressedпопросить сжатый ответ и распаковать его

JSON

bash
# классический способ
curl -X POST $API/users \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"name": "Анна", "role": "admin"}'

# то же короче, curl сам выставит оба заголовка и метод
curl --json '{"name": "Анна", "role": "admin"}' $API/users

# тело из файла
curl --json @user.json $API/users

# частичное обновление и удаление
curl -X PATCH --json '{"role": "viewer"}' $API/users/42
curl -X DELETE $API/users/42

Ключ -d отправляет данные как есть, но выбрасывает переводы строк, если читает из файла через @. Для бинарных данных и файлов, где переводы строк важны, используйте --data-binary @file.

Подставлять переменные оболочки внутрь JSON в одинарных кавычках нельзя: они не раскроются. Надёжнее собрать тело через jq:

bash
name="Анна \"Ника\""
jq -n --arg name "$name" '{name: $name, active: true}' | curl --json @- $API/users

@- означает «прочитать тело со стандартного ввода».

Формы и файлы

bash
# обычная форма: application/x-www-form-urlencoded
curl -d "login=anna" -d "remember=1" $API/login

# значение с пробелами и спецсимволами — с кодированием
curl --data-urlencode "comment=Цена 100% & доставка" $API/feedback

# multipart: поля и файлы
curl -F "title=Отчёт" -F "file=@report.pdf" $API/upload
curl -F "file=@photo.jpg;type=image/jpeg;filename=avatar.jpg" $API/avatar

# загрузить файл телом запроса, как делают хранилища объектов
curl -T backup.tar.gz $API/files/backup.tar.gz

Заголовки и авторизация

bash
curl -H "Authorization: Bearer $TOKEN" $API/me
curl -u anna:secret $API/admin          # Basic, curl сам закодирует в Base64
curl -u anna $API/admin                 # пароль спросит интерактивно
curl -H "X-Request-Id: $(uuidgen)" $API/orders
curl -H "Accept-Language: ru" $API/products
curl -A "my-script/1.0" $API/ping       # свой User-Agent

Строку Basic из заголовка можно раскодировать декодером Base64, а содержимое Bearer-токена в формате JWT показывает разбор JWT.

Cookie и сессии

bash
# войти и сохранить cookie в файл
curl -c cookies.txt -d "login=anna&password=secret" $API/login

# следующие запросы с этими cookie
curl -b cookies.txt $API/profile

# читать и обновлять одновременно
curl -b cookies.txt -c cookies.txt $API/cart

# передать cookie напрямую
curl -b "sid=a1b2c3; theme=dark" $API/profile

Перенаправления и скачивание

bash
curl -L $API/old-path                      # пройти по всем перенаправлениям
curl -L --max-redirs 5 $API/old-path
curl -sIL $API/old-path | grep -i -E "^(HTTP|location)"   # показать цепочку
curl -LO "$URL"                            # скачать под исходным именем
curl -L -o app.tar.gz -C - "$URL"          # докачать, если файл уже частично есть
curl -L --limit-rate 2M -O "$URL"          # не быстрее 2 МБ/с

По умолчанию curl не идёт по перенаправлениям и показывает ответ 301 или 302 как есть. Если скачанный «файл» весит пару сотен байт, загляните в него: скорее всего это страница перенаправления.

Скрипты: коды, повторы, таймауты

В скриптах важно, чтобы ошибка HTTP не выглядела как успех. Без --fail curl возвращает код выхода 0 даже на ответ 500.

bash
if ! curl -fsS -m 10 -o /dev/null "$API/health"; then
  echo "сервис не отвечает" >&2
  exit 1
fi

Только код ответа:

bash
code=$(curl -s -o /dev/null -w '%{http_code}' "$API/health")
[[ "$code" == 200 ]] || echo "получили $code"

Повторы при временных сбоях:

bash
curl -fsS --retry 5 --retry-delay 2 --retry-all-errors -m 30 "$API/report" -o report.json

--retry сам по себе повторяет только при сетевых ошибках, таймаутах и ответах 408, 429, 500, 502, 503 и 504. --retry-all-errors (curl 7.71 и новее) повторяет при любой ошибке; пользуйтесь им осторожно с запросами, которые что-то меняют.

Код выхода curlЗначение
0успех
6не удалось найти адрес по имени (DNS)
7не удалось подключиться
22сервер ответил 400 или больше, при --fail
28истёк таймаут
35ошибка TLS-соединения
60сертификат сервера не прошёл проверку

Сколько времени занимает запрос

Ключ -w выводит переменные после завершения запроса. Удобно разложить время по этапам:

bash
curl -s -o /dev/null -w '
dns:       %{time_namelookup}s
connect:   %{time_connect}s
tls:       %{time_appconnect}s
first byte:%{time_starttransfer}s
total:     %{time_total}s
code:      %{http_code}
size:      %{size_download} байт
' "$API/products"

Если основная часть времени уходит до первого байта, медленно работает приложение. Если велик connect или tls, дело в сети или настройке TLS. Шаблон можно положить в файл и подключать как -w @timing.txt.

Полезные мелочи

  • --resolve api.local:443:127.0.0.1 отправит запрос на указанный IP, сохранив имя хоста в TLS и Host. Так проверяют новый сервер до переключения DNS.
  • -H "Host: site.local" делает то же для обычного HTTP.
  • --http1.1 и --http2 принудительно выбирают версию протокола.
  • -K config.txt читает ключи из файла; удобно для длинных повторяющихся команд.
  • curl --version показывает версию и поддерживаемые возможности: если какого-то ключа нет, причина обычно здесь.
  • В браузере в инструментах разработчика, во вкладке «Сеть», у любого запроса есть пункт «Копировать как cURL». Получится готовая команда со всеми заголовками и cookie, только не публикуйте её: в ней могут быть токены.

Что означают коды ответа, разобрано в отдельной статье, а заголовки — в статье про заголовки HTTP.