HTTP и REST API
HTTP и REST API: методы (GET/POST/PUT/PATCH/DELETE), статус-коды, заголовки, тело запроса, REST, идемпотентность, аутентификация. Примеры curl и fetch.
HTTP (HyperText Transfer Protocol) — это текстовый протокол прикладного уровня, на котором держится почти весь обмен данными в вебе. Клиент (браузер, мобильное приложение, curl) шлёт запрос, сервер возвращает ответ. Протокол работает по схеме «запрос-ответ» и без состояния (stateless): каждый запрос самодостаточен, сервер не обязан помнить предыдущие. Эта шпаргалка собирает методы, статус-коды, заголовки, тело, принципы REST и аутентификацию в одном месте — с примерами curl и fetch.
Что такое HTTP: запрос и ответ
Обмен идёт парами: один запрос — один ответ. Запрос задаёт метод (что сделать), URL (с чем) и набор заголовков (метаданные), иногда — тело (данные). Ответ возвращает статус-код (как прошло), свои заголовки и обычно тело (результат).
Stateless означает, что HTTP сам по себе не хранит сессию. Чтобы «узнавать» клиента между запросами, используют куки (Cookie) или токены (Authorization). Современные версии: HTTP/1.1 (текстовый), HTTP/2 (бинарный, мультиплексирование), HTTP/3 (поверх QUIC/UDP) — но семантика методов и кодов одинакова.
# Самый простой запрос: получить страницу и показать заголовки ответа
curl -i https://api.example.com/ping
HTTP-методы (глаголы)
Метод задаёт намерение. Важны два свойства: безопасный (safe) — не меняет данные на сервере; идемпотентный — повторный одинаковый запрос даёт тот же результат, что и один.
| Метод | Назначение | Тело запроса | Безопасный | Идемпотентный |
|---|---|---|---|---|
| GET | Получить ресурс или список | нет | да | да |
| POST | Создать ресурс, отправить данные, запустить действие | да | нет | нет |
| PUT | Полностью заменить ресурс (или создать по известному URL) | да | нет | да |
| PATCH | Частично обновить ресурс | да | нет | нет* |
| DELETE | Удалить ресурс | обычно нет | нет | да |
| HEAD | Как GET, но только заголовки, без тела (проверка наличия/размера) | нет | да | да |
| OPTIONS | Узнать допустимые методы и CORS-политику (preflight) | нет | да | да |
* PATCH идемпотентен только если операция сформулирована так (например, «установить поле в значение»). Операции вида «увеличить на 1» не идемпотентны.
# GET — получить пользователя
curl https://api.example.com/users/42
# POST — создать пользователя
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Аня", "email": "[email protected]"}'
# PUT — полностью заменить
curl -X PUT https://api.example.com/users/42 \
-H "Content-Type: application/json" \
-d '{"name": "Аня", "email": "[email protected]"}'
# PATCH — обновить одно поле
curl -X PATCH https://api.example.com/users/42 \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]"}'
# DELETE — удалить
curl -X DELETE https://api.example.com/users/42
# HEAD — только заголовки (узнать размер файла)
curl -I https://api.example.com/files/report.pdf
# OPTIONS — какие методы разрешены
curl -X OPTIONS https://api.example.com/users -i
Структура HTTP-запроса
Запрос состоит из стартовой строки (метод + путь + версия), заголовков (по одному на строку, формат Имя: значение), пустой строки и необязательного тела.
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGci...
Content-Length: 48
{"name": "Аня", "email": "[email protected]"}
Стартовая строка: POST /users HTTP/1.1. Host обязателен в HTTP/1.1. Пустая строка отделяет заголовки от тела. Тело идёт только у методов, которые его поддерживают (POST/PUT/PATCH).
Структура HTTP-ответа
Ответ состоит из строки статуса (версия + код + текст), заголовков, пустой строки и тела.
HTTP/1.1 201 Created
Content-Type: application/json
Location: /users/42
Content-Length: 60
Date: Mon, 16 Jun 2026 10:00:00 GMT
{"id": 42, "name": "Аня", "email": "[email protected]"}
201 Created — код результата. Location указывает на созданный ресурс. Content-Type описывает формат тела. Дальше — пустая строка и сам JSON.
Статус-коды
Код состоит из трёх цифр; первая задаёт класс. Запоминать стоит классы и ключевые коды.
| Класс | Смысл |
|---|---|
| 1xx | Информационные — запрос принят, обработка продолжается (редко видны в коде) |
| 2xx | Успех — запрос принят и обработан |
| 3xx | Перенаправление — нужно доп. действие (часто переход по другому URL) |
| 4xx | Ошибка клиента — проблема в самом запросе |
| 5xx | Ошибка сервера — запрос корректен, но сервер не справился |
Ключевые коды
| Код | Название | Когда |
|---|---|---|
| 200 | OK | Успех, тело содержит результат (типично для GET) |
| 201 | Created | Ресурс создан (типично для POST), часто с заголовком Location |
| 204 | No Content | Успех, но тела нет (типично для DELETE, PUT без ответа) |
| 301 | Moved Permanently | Ресурс навсегда переехал на новый URL (кешируется) |
| 302 | Found | Временное перенаправление на другой URL |
| 304 | Not Modified | Ресурс не изменился, используйте кеш (ответ на условный запрос) |
| 400 | Bad Request | Некорректный запрос (битый JSON, не тот формат) |
| 401 | Unauthorized | Не аутентифицирован — нет/невалиден токен или логин |
| 403 | Forbidden | Аутентифицирован, но нет прав на действие |
| 404 | Not Found | Ресурс не найден по этому URL |
| 409 | Conflict | Конфликт состояния (например, дубликат, гонка версий) |
| 422 | Unprocessable Entity | Тело валидно по формату, но не проходит валидацию данных |
| 429 | Too Many Requests | Превышен лимит запросов (rate limit), см. Retry-After |
| 500 | Internal Server Error | Необработанная ошибка на сервере |
| 502 | Bad Gateway | Прокси/шлюз получил неверный ответ от вышестоящего сервера |
| 503 | Service Unavailable | Сервер временно недоступен (перегрузка, обслуживание) |
Разница 401 vs 403: 401 — «я не знаю, кто ты» (нет валидной аутентификации), 403 — «я знаю, кто ты, но тебе нельзя». 400 vs 422: 400 — запрос вообще не разобрать (битый синтаксис), 422 — разобрали, но данные не прошли бизнес-валидацию.
Заголовки (Headers)
Заголовки несут метаданные о запросе или ответе: формат тела, авторизацию, кеширование, куки. Имена регистронезависимы.
| Заголовок | Где | Назначение |
|---|---|---|
| Content-Type | запрос/ответ | Формат тела: application/json, text/html, application/x-www-form-urlencoded, multipart/form-data |
| Accept | запрос | Какие форматы ответа клиент готов принять |
| Authorization | запрос | Учётные данные: Bearer <token>, Basic <base64> |
| Cache-Control | запрос/ответ | Политика кеша: no-cache, max-age=3600, no-store |
| Cookie | запрос | Куки, отправляемые клиентом серверу |
| Set-Cookie | ответ | Сервер просит клиента сохранить куку |
| Content-Length | запрос/ответ | Размер тела в байтах |
| Location | ответ | URL созданного ресурса (201) или цель редиректа (3xx) |
| User-Agent | запрос | Идентификатор клиента (браузер/библиотека) |
| ETag | ответ | Версия ресурса для условных запросов и кеша |
| Retry-After | ответ | Через сколько повторить (при 429/503) |
CORS-заголовки
CORS (Cross-Origin Resource Sharing) разрешает браузеру обращаться к API на другом домене. Сервер отвечает специальными заголовками; для «сложных» запросов браузер сначала шлёт preflight (OPTIONS).
| Заголовок | Назначение |
|---|---|
| Access-Control-Allow-Origin | Какие источники могут обращаться (* или конкретный домен) |
| Access-Control-Allow-Methods | Разрешённые методы (GET, POST, PUT, DELETE) |
| Access-Control-Allow-Headers | Какие заголовки клиент может слать (например, Authorization, Content-Type) |
| Access-Control-Allow-Credentials | Разрешить отправку кук/учётных данных (true) |
| Access-Control-Max-Age | Сколько секунд кешировать результат preflight |
# Передать несколько заголовков
curl https://api.example.com/users/42 \
-H "Accept: application/json" \
-H "Authorization: Bearer eyJhbGci..." \
-H "Cache-Control: no-cache"
# Посмотреть preflight-ответ CORS
curl -X OPTIONS https://api.example.com/users \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" -i
Тело запроса: JSON и формы
Тело несут методы POST/PUT/PATCH. Формат указывается в Content-Type.
JSON (application/json)
Самый частый формат для REST API. Сервер парсит тело как JSON.
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Аня", "age": 25, "roles": ["admin", "editor"]}'
URL-кодированная форма (application/x-www-form-urlencoded)
Классические HTML-формы. Пары ключ=значение, разделённые &.
curl -X POST https://api.example.com/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=anya&password=secret"
Файлы (multipart/form-data)
Для загрузки файлов и смешанных данных.
curl -X POST https://api.example.com/upload \
-F "file=@/path/to/photo.jpg" \
-F "title=Моё фото"
Принципы REST
REST (Representational State Transfer) — архитектурный стиль для API поверх HTTP. Ключевые идеи:
- Ресурсы и URL. Всё — это ресурс с адресом. Существительные во множественном числе:
/users,/users/42,/users/42/orders. Не глаголы (/getUser— антипаттерн). - Методы как действия. Не кодируйте действие в URL — используйте HTTP-метод: GET читать, POST создавать, PUT/PATCH менять, DELETE удалять.
- Stateless. Сервер не хранит сессию между запросами; всё нужное (токен, параметры) приходит в каждом запросе.
- Представления. Один ресурс может отдаваться в разных форматах (JSON, XML) — клиент выбирает через
Accept. - Коды статусов по смыслу. 200/201/204 для успеха, 4xx для ошибок клиента, 5xx для ошибок сервера — а не «всегда 200 с полем error».
- Единообразие. Предсказуемые URL, пагинация (
?page=2&limit=20), фильтрация (?status=active), версионирование (/v1/users).
| Действие | Метод + URL | Типичный код |
|---|---|---|
| Список пользователей | GET /users | 200 |
| Один пользователь | GET /users/42 | 200 / 404 |
| Создать | POST /users | 201 |
| Заменить целиком | PUT /users/42 | 200 / 204 |
| Частично обновить | PATCH /users/42 | 200 |
| Удалить | DELETE /users/42 | 204 |
Идемпотентность методов
Идемпотентный метод можно повторять сколько угодно раз — состояние сервера будет таким же, как после одного запроса. Это важно для надёжных повторов при обрыве сети.
| Метод | Идемпотентный | Почему |
|---|---|---|
| GET | да | Только читает, ничего не меняет |
| HEAD | да | Как GET, без тела |
| OPTIONS | да | Только описывает возможности |
| PUT | да | Заменяет ресурс целиком — результат один и тот же |
| DELETE | да | Повторное удаление того же ресурса не меняет итог (его уже нет) |
| POST | нет | Каждый вызов обычно создаёт новый ресурс |
| PATCH | нет* | Зависит от операции (замена — да, инкремент — нет) |
Практика: повторять автоматически безопасно только идемпотентные запросы. Для POST, чтобы избежать дублей, используют ключ идемпотентности (Idempotency-Key).
Аутентификация
HTTP сам по себе не помнит клиента, поэтому учётные данные шлют в каждом запросе — обычно в заголовке Authorization.
Basic Auth
Логин и пароль в виде base64(login:password). Просто, но передаёт пароль почти в открытом виде — только поверх HTTPS.
# curl сам закодирует логин:пароль в base64
curl -u anya:secret https://api.example.com/private
# Эквивалент вручную:
curl https://api.example.com/private \
-H "Authorization: Basic YW55YTpzZWNyZXQ="
Bearer / JWT
Токен (часто JWT) передаётся как Bearer <token>. JWT — это самодостаточный токен из трёх частей (header.payload.signature), подписанный сервером.
curl https://api.example.com/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
API-ключ
Простой статичный ключ — в заголовке или query-параметре. Удобен для сервер-сервер интеграций.
# В отдельном заголовке
curl https://api.example.com/data -H "X-API-Key: ab12cd34ef56"
# Или как параметр URL (менее безопасно — попадает в логи)
curl "https://api.example.com/data?api_key=ab12cd34ef56"
| Способ | Заголовок | Плюсы / минусы |
|---|---|---|
| Basic | Authorization: Basic ... | Простой; пароль в каждом запросе |
| Bearer/JWT | Authorization: Bearer ... | Без пароля, с истечением срока; нужно обновлять токен |
| API-key | X-API-Key: ... | Удобно для интеграций; ключ долгоживущий, легко утекает |
Примеры: curl и fetch
GET с авторизацией
curl https://api.example.com/users/42 \
-H "Authorization: Bearer TOKEN"
const res = await fetch("https://api.example.com/users/42", {
headers: { "Authorization": "Bearer TOKEN" }
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();
POST с JSON
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN" \
-d '{"name": "Аня", "email": "[email protected]"}'
const res = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer TOKEN"
},
body: JSON.stringify({ name: "Аня", email: "[email protected]" })
});
const created = await res.json(); // ожидаем статус 201
PATCH и обработка ошибок
const res = await fetch("https://api.example.com/users/42", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "[email protected]" })
});
if (res.status === 422) {
const errors = await res.json(); // данные не прошли валидацию
console.error("Ошибки валидации:", errors);
} else if (res.status === 401) {
console.error("Нужна авторизация");
} else if (res.ok) {
console.log("Обновлено:", await res.json());
}
DELETE
curl -X DELETE https://api.example.com/users/42 \
-H "Authorization: Bearer TOKEN" -i # ждём 204 No Content
const res = await fetch("https://api.example.com/users/42", {
method: "DELETE",
headers: { "Authorization": "Bearer TOKEN" }
});
console.log(res.status === 204 ? "Удалён" : "Не удалось");