Урок 07.01: Основы REST API (методы, статус-коды)
Цель урока
Познакомиться с архитектурой REST API — стандартом взаимодействия между клиентом и сервером в современных веб-системах. Научиться различать HTTP-методы (GET, POST, PUT, PATCH, DELETE), понимать статус-коды (2xx, 3xx, 4xx, 5xx) и проектировать простые RESTful-эндпоинты. Особый фокус: катастрофические последствия неправильного выбора между PUT и PATCH, а также «молекулярное» устройство HTTP-пакета: заголовки Content-Type, Accept и механизм Content Negotiation.
Ключевые понятия
| Термин | Определение |
|---|---|
| API | Application Programming Interface — контракт, определяющий, как одна программа может общаться с другой |
| REST | Representational State Transfer — архитектурный стиль для проектирования API, основанный на ресурсах и HTTP-методах |
| RESTful API | API, следующий принципам REST |
| Ресурс (Resource) | Сущность, с которой работает API (пользователь, задача, заказ) |
| Эндпоинт (Endpoint) | Конкретный URL, по которому доступен ресурс (/api/v1/tasks/42) |
| HTTP-метод | Глагол, указывающий на действие: GET, POST, PUT, PATCH, DELETE |
| HTTP-статус | Трёхзначный код ответа сервера (200, 404, 500) |
| Заголовок (Header) | Мета-информация запроса/ответа (Content-Type, Authorization, Accept) |
| Content Negotiation | Механизм согласования формата данных между клиентом и сервером |
| Идемпотентность | Свойство: повторный вызов метода даёт тот же результат |
| MIME-тип | Стандартный идентификатор формата данных (application/json, text/xml) |
1. Что такое API и зачем он аналитику?
1.1. Аналогия: API — это официант в ресторане
Представьте, что вы пришли в ресторан. Вы не можете зайти на кухню и приготовить пасту сами — у вас нет доступа к плите, ингредиентам и рецептам (это сервер). Вместо этого вы зовёте официанта — это API. Официант принимает ваш заказ (запрос), передаёт его на кухню (сервер), а затем приносит готовое блюдо (ответ).
| Элемент ресторана | IT-аналог |
|---|---|
| Вы (клиент) | Клиентское приложение (веб, мобильное) |
| Официант | API — точка входа |
| Меню | Документация API (что можно заказать) |
| Кухня | Сервер / Бэкенд |
| Готовое блюдо | HTTP-ответ (данные в JSON) |
Почему это важно для аналитика: Когда вы описываете интеграцию «CRM обменивается данными с 1С через REST API» — вы описываете именно эту схему. CRM — клиент, 1С — сервер (или наоборот). Аналитик должен понимать, как формируется «заказ» и что приносит «официант».
1.2. Зачем аналитику знать REST API
| Задача аналитика | Связь с API |
|---|---|
| Описание интеграции | «Система A отправляет заказы в систему B через REST API» — нужно знать формат запроса, адрес эндпоинта, метод |
| Формирование требований к API | «Разработать эндпоинт для получения списка задач с пагинацией» — аналитик описывает, что должен вернуть GET /tasks |
| Валидация API-контракта | Проверить по Postman, что статус-коды соответствуют сценариям (201 при создании, 404 при отсутствии) |
| Анализ ошибок интеграции | «Почему упала интеграция с 1С?» — аналитик смотрит HTTP-статус и тело ответа |
| Чтение OpenAPI-спецификации | Почти все современные API описываются в OpenAPI (Swagger) — это формат документации, который аналитик должен уметь читать |
Золотое правило: Если вы не понимаете, чем PUT отличается от PATCH и почему сервер вернул 409 вместо 422 — вы не сможете описать корректное API-взаимодействие. Разработчик скажет: «аналитик написал ерунду, я сделал по-своему». Ваш API-контракт должен быть настолько точным, чтобы его можно было реализовать без вопросов.
2. Принципы REST
2.1. Шесть принципов REST (Roy Fielding, 2000)
REST — не протокол, а архитектурный стиль. Шесть ограничений, которым должна следовать система, чтобы называться RESTful:
| № | Принцип | Описание | Пример |
|---|---|---|---|
| 1 | Client-Server (разделение клиента и сервера) | Клиент не заботится о том, как сервер хранит данные. Сервер не заботится о том, как клиент их отображает | Мобильное приложение (Kotlin) и сервер (Java) — разные команды, разные технологии |
| 2 | Stateless (без состояния) | Каждый запрос содержит всю информацию, необходимую для его обработки. Сервер не хранит сессию | В каждом запросе — токен авторизации |
| 3 | Cacheable (кэшируемость) | Ответы должны явно указывать, можно ли их кэшировать | GET /tasks → заголовок Cache-Control: max-age=300 |
| 4 | Uniform Interface (единый интерфейс) | Единые правила для всех ресурсов: одинаковые URL-шаблоны, одинаковые методы | Все ресурсы используют GET/POST/PUT/DELETE по одинаковым правилам |
| 5 | Layered System (слои) | Клиент не знает, общается ли он напрямую с сервером или через прокси/gateway | Client → Nginx (proxy) → Application Server → DB |
| 6 | Code on Demand (опционально) | Сервер может передавать исполняемый код клиенту | JavaScript-скрипт в ответе (редко используется) |
2.2. Stateless — самый важный принцип для REST
Как НЕ надо (с сессией):
Запрос 1: POST /login → Сервер создаёт сессию, сохраняет session_id в своей памяти
и отдаёт клиенту куку session_id=abc123
Запрос 2: GET /tasks (с Cookie: session_id=abc123)
→ Сервер ищет сессию abc123 в своей памяти, находит, проверяет права
→ Проблема: сессия хранится НА СЕРВЕРЕ. Если серверов 3 — сессия может быть на первом,
а запрос ушёл на второй. Нужен sticky session или Redis.
Как надо (Stateless, с токеном):
Запрос 1: POST /auth/login → Сервер проверяет email+password, создаёт JWT-токен,
который содержит user_id и роль ЦИФРОВОЙ ПОДПИСЬЮ.
Сервер НЕ хранит токен в своей памяти.
Запрос 2: GET /tasks (с Authorization: Bearer <JWT>)
→ Любой сервер из трёх может проверить подпись JWT, НЕ обращаясь к общему хранилищу.
→ Stateless — ключевое свойство для горизонтального масштабирования.
Для аналитика: Если вы в требованиях к API видите «сервер создаёт сессию» — это не RESTful. RESTful API использует токены (JWT, OAuth2), не храня состояние на сервере.
3. HTTP-методы (глаголы REST)
3.1. Основные методы
| Метод | Действие | Идемпотентность | Безопасный (Safe) | Аналог SQL |
|---|---|---|---|---|
| GET | Прочитать ресурс | ✅ Да | ✅ Да (не меняет данные) | SELECT |
| POST | Создать ресурс (или выполнить действие) | ❌ Нет | ❌ Нет | INSERT |
| PUT | Полностью заменить ресурс | ✅ Да | ❌ Нет | REPLACE (или DELETE + INSERT) |
| PATCH | Частично изменить ресурс | ❌ Нет (но можно сделать идемпотентным) | ❌ Нет | UPDATE |
| DELETE | Удалить ресурс | ✅ Да | ❌ Нет | DELETE |
Идемпотентность простыми словами: если вы вызываете метод 1 раз или 10 раз — результат (состояние сервера) одинаков.
- GET /tasks/42 — сколько ни вызывай, ответ тот же (если данные не меняются между вызовами) — идемпотентен
- DELETE /tasks/42 — первый раз удалил (204), второй раз — 404 (но состояние: задачи нет в обоих случаях) — идемпотентен (состояние одинаково)
- POST /tasks — каждый вызов создаёт новую задачу с новым id — НЕ идемпотентен
- PUT /tasks/42 — заменил содержимое задачи. 10 раз вызвал — состояние то же — идемпотентен
- PATCH /tasks/42 — увеличил счётчик на 1. 10 раз вызвал — счётчик = 10 — НЕ идемпотентен
3.2. Примеры: как методы применяются к ресурсам
| Ресурс | GET (прочитать) | POST (создать) | PUT (заменить) | PATCH (изменить) | DELETE (удалить) |
|---|---|---|---|---|---|
/tasks |
Список задач | Создать задачу | ❌ (массовая замена — редко) | ❌ | ❌ |
/tasks/{id} |
Конкретная задача | ❌ | Полная перезапись задачи | Обновить поля (status) | Удалить задачу |
/users |
Список пользователей | Создать пользователя | ❌ | ❌ | ❌ |
/users/{id} |
Конкретный пользователь | ❌ | Полная перезапись профиля | Обновить email | Удалить |
/projects/{id}/tasks |
Задачи проекта | Создать задачу в проекте | ❌ | ❌ | ❌ |
3.3. Важно: что означает «заменить» для PUT
PUT — это полная замена ресурса. Клиент отправляет весь объект целиком. Сервер берёт этот объект и заменяет им существующий. Если клиент не указал какое-то поле — оно будет стёрто (приравнено к умолчанию или NULL).
PUT против PATCH — кратко:
// PUT — замена целиком: нужно передать ВСЕ поля
PUT /tasks/42
Content-Type: application/json
{
"id": 42,
"title": "Исправить баг",
"description": "Баг в авторизации",
"status": "In Progress",
"priority": "Critical",
"assignee_id": 1,
"project_id": 1,
"deadline": "2026-06-10"
}
// PATCH — частичное изменение: только то, что меняем
PATCH /tasks/42
Content-Type: application/json
{
"status": "Done"
}
4. ⚠️ КАТАСТРОФА: Когда PUT затирает данные в NULL
4.1. Реальная история: «Исчезнувшее описание товара»
Контекст: Интернет-магазин. Товар имеет поля: id, title, description, price, category_id, image_url, status, weight, dimensions.
Было (в БД):
| id | title | description | price | category_id | image_url | status | weight |
|---|---|---|---|---|---|---|---|
| 42 | Стул офисный | «Эргономичный стул с поддержкой поясницы...» | 12500 | 5 | /images/chair.jpg | active | 12.5 |
Событие: Frontend-разработчик получил задачу «Сделать форму редактирования цены товара». Он отправляет PUT-запрос:
PUT /api/v1/products/42
Content-Type: application/json
{
"id": 42,
"price": 9900
}
Он хотел изменить только цену, но использовал PUT. А PUT, по определению — полная замена. Сервер (написанный на Spring Boot / Express / Django) делает:
# Псевдокод: PUT /products/{id}
data = request.json() # { "id": 42, "price": 9900 }
product = Product.query.get(id) # Загружаем из БД
product.update(data) # Заменяем ВСЕ поля тем, что пришло
product.save()
Что стало в БД:
| id | title | description | price | category_id | image_url | status | weight |
|---|---|---|---|---|---|---|---|
| 42 | NULL | NULL | 9900 | NULL | NULL | NULL | NULL |
Все поля, которых не было в PUT-запросе, стали NULL. Описание товара исчезло. Картинка пропала. Категория обнулилась.
4.2. Бизнес-последствия (цепочка катастрофы)
- Менеджер заметил, что цена стала 9900 (всё верно)
- Через час клиенты видят товар без названия, без картинки, без описания
- Отдел маркетинга получает жалобы: «У вас товар без названия»
- Администратор идёт в админку, видит NULL в полях
- Выясняется, что все товары, которые редактировали через новую форму, потеряли часть данных
- Если данные не восстанавливаются из бэкапа — потери необратимы
Оценка ущерба:
- 150 товаров с потерянными описаниями
- 3 дня работы контент-менеджеров на восстановление
- Падение конверсии на 15% (товары без описания не покупают)
- Потеря SEO-позиций (описания — ключевой контент для поисковиков)
4.3. Анатомия ошибки: почему это произошло?
Причина — семантическая разница между PUT и PATCH, закреплённая в спецификации HTTP (RFC 7231 для PUT, RFC 5789 для PATCH).
| Аспект | PUT | PATCH |
|---|---|---|
| Что делает | Заменяет ресурс целиком | Применяет частичные изменения |
| Что если поле не указано | Устанавливается в значение по умолчанию (или NULL) | Остаётся без изменений |
| Идемпотентность | ✅ Да | ❌ Нет (но может быть спроектирована идемпотентно) |
| RFC | RFC 7231, Section 4.3.4 | RFC 5789 (изначально), RFC 7396 (JSON Merge Patch) |
Цитата из RFC 7231 (PUT):
«The PUT method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload.»
То есть: «замени состояние ресурса тем, что в теле запроса». Если в теле нет description — значит, description = NULL.
4.4. Как правильно: PATCH с JSON Merge Patch (RFC 7396)
Правильный запрос (меняем только цену):
PATCH /api/v1/products/42
Content-Type: application/merge-patch+json
{
"price": 9900
}
Сервер понимает: «это PATCH, нужно применить только переданные поля, остальные не трогать».
# Псевдокод: PATCH /products/{id}
data = request.json() # { "price": 9900 }
product = Product.query.get(id)
for key, value in data.items(): # Обходим ТОЛЬКО ключи из запроса
setattr(product, key, value) # Меняем только price
product.save()
Результат: цена изменилась, описание и картинка не тронуты.
4.5. Как аналитик может предотвратить эту катастрофу
-
В спецификации API всегда указывайте: «Для частичного обновления используйте PATCH с Partial Update Body (JSON Merge Patch)». Не пишите «PUT для обновления» — разработчик может сделать PUT там, где нужен PATCH.
-
Документируйте поля обязательные для PUT: Если метод PUT — перечислите ВСЕ поля, которые клиент ОБЯЗАН отправить. Укажите: «Отсутствующие поля будут сброшены в NULL».
-
Разделяйте эндпоинты: Если нужно только сменить статус — спроектируйте отдельный эндпоинт:
PATCH /tasks/42/status
Content-Type: application/json
{ "status": "Done" }
Или более RESTful:
POST /tasks/42/change-status
Content-Type: application/json
{ "status": "Done" }
-
В контракте (OpenAPI) опишите разницу:
PUT /tasks/{id}— полная перезапись задачи, все поля обязательныPATCH /tasks/{id}— частичное обновление, передаются только изменяемые поля
-
Проверяйте на Code Review: Если видите в коде клиента PUT с 2 полями из 10 — это баг. Нужен PATCH.
4.6. Сравнительная таблица: когда какой метод использовать
| Сценарий | Метод | Почему |
|---|---|---|
| Создать задачу | POST | Нет id до создания, неидемпотентно |
| Получить задачу | GET | Только чтение |
| Полностью перезаписать задачу (восстановить из бэкапа) | PUT | Нужна полная замена |
| Сменить статус задачи с "In Progress" на "Done" | PATCH | Меняется 1 поле из 10 |
| Увеличить счётчик просмотров на 1 | PATCH | Частичное изменение |
| Обновить email пользователя | PATCH | 1 поле из 8 |
| Массовая замена (заменить все задачи проекта) | PUT на коллекцию | Но обычно так не делают |
4.7. Дополнительно: JSON Patch (RFC 6902)
Стандарт RFC 6902 определяет формат для описания операций изменения:
PATCH /tasks/42
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/status", "value": "Done" },
{ "op": "add", "path": "/tags/-", "value": "urgent" }
]
Это более мощный, но более громоздкий формат. На практике JSON Merge Patch (просто передать поля) используется чаще.
5. HTTP-статус-коды
5.1. Категории
| Категория | Диапазон | Смысл |
|---|---|---|
| 1xx Informational | 100–199 | Информационные (редко используются в API) |
| 2xx Success | 200–299 | Успешная обработка запроса |
| 3xx Redirection | 300–399 | Перенаправление (клиент должен выполнить новый запрос) |
| 4xx Client Error | 400–499 | Ошибка на стороне клиента (неверные данные, нет прав) |
| 5xx Server Error | 500–599 | Ошибка на стороне сервера (сломался код, БД недоступна) |
5.2. Полная таблица статус-кодов для аналитика
| Код | Название | Когда возвращается | Типичный метод |
|---|---|---|---|
| 200 OK | Успех | GET — данные найдены, PUT/PATCH — обновлено | GET, PUT, PATCH |
| 201 Created | Создано | POST — ресурс создан | POST |
| 204 No Content | Нет содержимого | DELETE — удалено успешно (тело ответа пустое) | DELETE |
| 301 Moved Permanently | Перемещено навсегда | Ресурс переехал на новый URL, клиент должен обновить ссылку | Любой |
| 304 Not Modified | Не изменялось | Клиент отправил If-Modified-Since, данные не менялись | GET |
| 400 Bad Request | Неверный запрос | Невалидный JSON (синтаксическая ошибка) | POST, PUT, PATCH |
| 401 Unauthorized | Не авторизован | Нет токена авторизации, токен истёк | Любой |
| 403 Forbidden | Доступ запрещён | Токен есть, но у пользователя нет прав на этот ресурс | Любой |
| 404 Not Found | Не найдено | Ресурс с таким ID не существует | GET, PUT, PATCH, DELETE |
| 405 Method Not Allowed | Метод не разрешён | GET на /tasks работает, DELETE — нет | Любой |
| 409 Conflict | Конфликт | Попытка создать дубликат (email уже занят), состояние не позволяет | POST, PUT, PATCH |
| 415 Unsupported Media Type | Неподдерживаемый тип данных | Клиент отправил XML, а сервер принимает только JSON | POST, PUT, PATCH |
| 422 Unprocessable Entity | Необрабатываемая сущность | JSON валидный, но семантически некорректен (email = "не-email") | POST, PUT, PATCH |
| 429 Too Many Requests | Слишком много запросов | Клиент превысил Rate Limit (100 запросов в минуту) | Любой |
| 500 Internal Server Error | Внутренняя ошибка сервера | Непредвиденное исключение (NullPointerException, падение БД) | Любой |
| 502 Bad Gateway | Плохой шлюз | Nginx не может связаться с application server | Любой |
| 503 Service Unavailable | Сервис недоступен | Сервер на техническом обслуживании, перегрузка | Любой |
5.3. Сценарная таблица: какой код ожидать
| Сценарий | Метод | Статус |
|---|---|---|
| Успешное получение списка задач | GET /tasks | 200 |
| Успешное создание задачи | POST /tasks | 201 |
| Успешная смена статуса задачи | PATCH /tasks/42 | 200 |
| Успешное удаление задачи | DELETE /tasks/42 | 204 |
| Запрос несуществующей задачи | GET /tasks/999 | 404 |
| Создание задачи без названия (обязательное поле) | POST /tasks | 422 |
| Создание задачи с истёкшим токеном | POST /tasks | 401 |
| Попытка удалить задачу, назначенную на другого | DELETE /tasks/42 | 403 |
| Создание пользователя с существующим email | POST /users | 409 |
| Отправка данных в XML вместо JSON | POST /tasks | 415 |
| Превышение лимита запросов | GET /tasks | 429 |
| Сервер упал с NullPointerException | POST /tasks | 500 |
Для аналитика: В спецификации API для каждого эндпоинта нужно указывать все возможные статус-коды, не только успешные. Если вы не укажете 409 — разработчик не обработает конфликт, и пользователь получит 500 вместо понятного сообщения «Такой email уже зарегистрирован».
6. Структура URL (эндпоинты)
6.1. Анатомия URL
https://api.example.com:443/api/v1/tasks/42/comments?page=2&limit=10#section
│ │ │ │ │ │ │ │ │ │
scheme host port prefix версия ресурс id подресурс query-параметры fragment
| Компонент | Пример | Назначение |
|---|---|---|
| Scheme | https |
Протокол (обязательно HTTPS, не HTTP) |
| Host | api.example.com |
Домен сервера |
| Port | :443 |
По умолчанию 443 для HTTPS (можно не указывать) |
| Prefix | /api |
Отличие API от остальных маршрутов |
| Version | /v1 |
Версия API |
| Resource | /tasks |
Имя ресурса (существительное, множественное число) |
| ID | /42 |
Идентификатор конкретного ресурса |
| Sub-resource | /comments |
Вложенный ресурс (комментарии задачи 42) |
| Query | ?page=2&limit=10 |
Параметры фильтрации, пагинации |
| Fragment | #section |
Якорь (не передаётся на сервер, только для клиента) |
6.2. Правила именования ресурсов
| ✅ Правильно | ❌ Неправильно | Почему |
|---|---|---|
/tasks |
/get_tasks |
Ресурс — существительное, а метод (GET) уже говорит о чтении |
/tasks/{id} |
/delete_task_by_id/{id} |
Метод DELETE говорит об удалении, не нужно дублировать в URL |
/users/{id}/tasks |
/get_user_tasks |
Вложенность через /, а не через подчёркивание |
/api/v1/tasks |
/api/tasks |
Версионирование — обязательно, иначе сломаете старых клиентов |
/tasks?status=done |
/tasks/status/done |
Фильтры — query-параметры, не path |
/tasks |
/task |
Множественное число — стандарт (коллекция) |
6.3. Path-параметры vs Query-параметры
| Тип | Пример | Назначение | Ограничения |
|---|---|---|---|
| Path (часть пути) | /tasks/{id} |
Идентификация конкретного ресурса | Обязателен, обычно число или UUID |
| Path (составной) | /projects/{pid}/tasks/{tid} |
Идентификация вложенного ресурса | Иерархия: сначала проект, потом задача |
| Query | ?status=done |
Фильтрация списка | Опционально, может отсутствовать |
| Query | ?page=2&limit=10 |
Пагинация | Опционально, есть значения по умолчанию |
| Query | ?search=стул |
Поиск/сортировка | Может быть несколько |
7. 🧬 HTTP-пакет: молекулярный разбор
7.1. Из чего состоит HTTP-запрос
Каждый HTTP-запрос состоит из трёх частей:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. СТАРТОВАЯ СТРОКА (Request Line)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
POST /api/v1/tasks HTTP/1.1
│ │ │
метод URL версия HTTP
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2. ЗАГОЛОВКИ (Headers) — мета-информация
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Host: api.example.com # Кому (обязательный)
Content-Type: application/json # Что в теле запроса
Accept: application/json # Что клиент хочет в ответ
Authorization: Bearer eyJhbG... # Кто я
User-Agent: Mozilla/5.0 ... # Какой клиент
Content-Length: 128 # Размер тела в байтах
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3. ТЕЛО (Body) — содержимое (необязательно, например GET без тела)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{
"title": "Настроить CI/CD",
"description": "GitHub Actions",
"priority": "High"
}
7.2. Из чего состоит HTTP-ответ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. СТАТУСНАЯ СТРОКА (Status Line)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
HTTP/1.1 201 Created
│ │ │
версия код текст статуса
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2. ЗАГОЛОВКИ (Response Headers)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Content-Type: application/json # Формат ответа
Content-Length: 256 # Размер
Cache-Control: max-age=60 # Кэшировать 60 секунд
Set-Cookie: session_id=abc; HttpOnly # Установить куку
X-Request-Id: 7a1b2c3d # Трейсинг запроса
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3. ТЕЛО (Body)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{
"id": 42,
"title": "Настроить CI/CD",
"status": "To Do",
"created_at": "2026-05-29T10:30:00Z"
}
7.3. Content-Type: зачем серверу знать, что в теле?
Заголовок Content-Type в запросе сообщает серверу, в каком формате передано тело запроса. Без этого заголовка сервер не знает, как разобрать тело.
Пример: один и тот же ресурс в разных форматах
POST /api/v1/tasks
Content-Type: application/json
{"title": "Настроить CI/CD", "priority": "High"}
Против:
POST /api/v1/tasks
Content-Type: application/xml
<task>
<title>Настроить CI/CD</title>
<priority>High</priority>
</task>
Сервер, получив запрос, читает Content-Type и выбирает правильный парсер (JSON-парсер или XML-парсер).
Что если Content-Type не совпадает с реальным форматом?
Клиент отправляет:
POST /api/v1/tasks
Content-Type: application/json
<task><title>Настроить CI/CD</title></task> <!-- XML внутри! -->
Сервер попытается распарсить XML как JSON — получит SyntaxError и вернёт 400 Bad Request.
Для аналитика: В спецификации API нужно указывать: «все запросы должны содержать заголовок Content-Type: application/json». Если клиент не укажет Content-Type — сервер может не принять запрос.
7.4. Accept: как клиент говорит «я хочу JSON, не XML»
Заголовок Accept — это клиент говорит серверу: «Пришли мне ответ в таком-то формате, я умею его обрабатывать».
Пример Accept:
GET /api/v1/tasks/42
Accept: application/json
Сервер видит Accept: application/json и обязан вернуть ответ в формате JSON. Заголовок ответа будет:
HTTP/1.1 200 OK
Content-Type: application/json
Если клиент хочет XML:
GET /api/v1/tasks/42
Accept: application/xml
Сервер возвращает:
HTTP/1.1 200 OK
Content-Type: application/xml
<task>
<id>42</id>
<title>Настроить CI/CD</title>
</task>
Один эндпоинт — два формата ответа. Без Accept сервер не знает, что отдать.
7.5. Content Negotiation: как сервер выбирает формат
Content Negotiation (согласование содержимого) — процесс, в котором сервер выбирает наилучший формат ответа на основе заголовка Accept.
Пошагово:
- Клиент отправляет запрос с
Accept: application/json; q=1.0, application/xml; q=0.8 - Сервер проверяет: какие форматы ответа он умеет генерировать (например, JSON и XML)
- Сервер смотрит на приоритет (q — quality factor): JSON с q=1.0 (максимальный), XML с q=0.8 (ниже)
- Сервер выбирает JSON (высший приоритет)
- Сервер устанавливает
Content-Type: application/jsonв ответе - Сервер отправляет тело в JSON
Качество (q-фактор):
Accept: application/json; q=1.0, application/xml; q=0.5— JSON в 2 раза предпочтительнееAccept: application/json, */*; q=0.1— JSON идеально, любой другой формат — если нет JSON- Если q не указан — подразумевается 1.0
Что если сервер не умеет ни в один из запрошенных форматов?
GET /api/v1/tasks/42
Accept: application/ms-excel
Сервер, который умеет только JSON и XML, возвращает:
HTTP/1.1 406 Not Acceptable
406 Not Acceptable — сервер не может предоставить данные в запрошенном клиентом формате.
7.6. Почему это важно для аналитика?
Реальный кейс: Система А (на Java) отправляет данные в Систему Б (на .NET). Система А по умолчанию шлёт XML. Система Б ожидает JSON.
Ситуация:
Запрос от Системы А (БЕЗ Accept):
POST /api/v1/orders
Content-Type: application/xml
<order>...</order>
Ответ от Системы Б:
HTTP/1.1 200 OK
Content-Type: application/json ← Ответ в JSON
{ "id": 42, "status": "created" }
Казалось бы, всё работает. Но если Система А не умеет парсить JSON (только XML) — она упадёт с ошибкой. Система Б должна была установить Content-Type: application/xml, если клиент (система А) указал Accept: application/xml. Но Accept не был указан — сервер выбрал JSON по умолчанию.
Правильное поведение:
- Система А отправляет
Accept: application/xml - Система Б проверяет Accept, видит
application/xml, умеет в XML - Система Б возвращает
Content-Type: application/xml - ✅ Интеграция работает
Что делает аналитик: В контракте интеграции прописывает: «Система А отправляет заголовок Accept: application/json» или «Система Б поддерживает оба формата: JSON и XML, выбор по Accept».
7.7. Сводная таблица заголовков Content-Type и Accept
| Заголовок | Где | Что означает | Пример |
|---|---|---|---|
| Content-Type | Запрос (Request) | Формат тела запроса | Content-Type: application/json |
| Content-Type | Ответ (Response) | Формат тела ответа | Content-Type: application/json; charset=utf-8 |
| Accept | Запрос (Request) | Какой формат ответа ожидает клиент | Accept: application/json |
| Accept-Language | Запрос (Request) | Какой язык предпочитает клиент | Accept-Language: ru-RU, ru; q=0.9 |
| Accept-Encoding | Запрос (Request) | Какое сжатие поддерживает клиент | Accept-Encoding: gzip, deflate |
7.8. MIME-типы: стандартные обозначения форматов
| MIME-тип | Формат | Использование |
|---|---|---|
application/json |
JSON | Стандарт для REST API |
application/xml |
XML | Устаревает, но ещё встречается в SOAP/старых системах |
text/plain |
Обычный текст | Простые ответы, healthcheck |
text/html |
HTML | Веб-страницы (не API) |
application/octet-stream |
Бинарные данные | Файлы, изображения |
multipart/form-data |
Составные данные | Загрузка файлов (form-data) |
application/x-www-form-urlencoded |
URL-кодированные данные | Старые HTML-формы |
application/merge-patch+json |
JSON Merge Patch | Частичное обновление (PATCH) |
application/problem+json |
JSON Problem Details | Стандартный формат ошибки (RFC 7807) |
application/vnd.api+json |
JSON:API | Спецификация JSON:API (сложные запросы) |
8. Тело запроса и ответа
8.1. Структура JSON-запроса (POST / PUT / PATCH)
POST — создание ресурса (сервер генерирует id):
{
"title": "Настроить CI/CD",
"description": "Настроить GitHub Actions для проекта Backend",
"priority": "High",
"assignee_id": 1,
"project_id": 1,
"deadline": "2026-06-01"
}
PUT — полная замена (id обязателен):
{
"id": 42,
"title": "Новое название",
"description": "Новое описание",
"status": "Done",
"priority": "High",
"assignee_id": 1,
"project_id": 1,
"deadline": "2026-06-10"
}
PATCH — частичное изменение (только изменяемые поля):
{
"status": "Done"
}
8.2. Структура ответа с ошибкой (стандарт Problem Details)
Хороший API возвращает ошибки в едином формате (RFC 7807 Problem Details):
{
"type": "https://api.example.com/errors/validation-error",
"title": "Ошибка валидации",
"status": 422,
"detail": "Поле 'email' имеет неверный формат",
"instance": "/api/v1/users",
"errors": {
"email": ["неверный формат", "уже зарегистрирован"],
"password": ["минимум 8 символов"]
}
}
Для аналитика: В требованиях к API нужно указать формат ошибки. Иначе каждый разработчик сделает «свой» формат, и клиенты будут путаться.
9. Аутентификация и авторизация в REST API
9.1. Basic Auth (устаревший, небезопасный)
Механизм: Заголовок Authorization: Basic base64(login:password).
GET /api/v1/tasks
Authorization: Basic YW5uYUBtYWlsLmNvbTpwYXNzMTIz
Недостатки:
- Пароль передаётся в каждом запросе (не хранится на сервере, но передаётся по сети)
- Если перехватить HTTPS — пароль скомпрометирован навсегда
- Нет возможности ограничить права (токен даёт полный доступ)
9.2. Bearer Token (JWT) — стандарт для REST
Механизм:
POST /auth/loginс email+password → сервер возвращает JWT-токен- Клиент сохраняет токен и шлёт его в каждом запросе
GET /api/v1/tasks
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiZGV2ZWxvcGVyIn0.9Z0R6A...
JWT состоит из трёх частей (разделены точками):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 — HEADER (алгоритм, тип токена)
.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiZGV2In0 — PAYLOAD (данные: user_id, role)
.9Z0R6A... — SIGNATURE (подпись, защита от подделки)
Преимущества:
- Stateless: сервер не хранит токен в БД
- Масштабирование: любой сервер может проверить подпись
- Срок действия: токен истекает через N минут
9.3. API Key (для интеграций «система-система»)
Когда две системы общаются без участия человека — используется статический ключ:
GET /api/v1/orders
X-API-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Или:
GET /api/v1/orders?api_key=a1b2c3d4-e5f6-7890-abcd-ef1234567890
10. Версионирование API
10.1. Зачем
API меняется. Новая версия не должна ломать старых клиентов. Представьте: вы выпустили мобильное приложение, а через месяц поменяли формат ответа — приложение у всех упало. Версионирование решает эту проблему: старые клиенты стучатся на /api/v1/, новые — на /api/v2/.
10.2. Три способа
| Способ | Пример | Плюсы | Минусы |
|---|---|---|---|
| URL path (самый популярный) | /api/v1/tasks, /api/v2/tasks |
✅ Простота, видно в логах, легко тестировать | Версия «зашита» в каждый URL |
| Header (Accept) | Accept: application/vnd.app.v1+json |
✅ URL остаётся чистым | ❌ Не видно в логах, сложно тестировать в браузере |
| Query param | /api/tasks?version=1 |
✅ Простота | ❌ Загрязняет URL, легко забыть |
10.3. Когда менять версию
Мажорная версия (v1 → v2):
- Удаление поля из ответа
- Изменение формата данных
- Удаление эндпоинта
- Изменение обязательности полей
Минорная версия (v1.1 → v1.2):
- Добавление нового поля в ответ (старый клиент его игнорирует)
- Добавление нового эндпоинта
- Добавление нового query-параметра
11. Пагинация (Pagination)
11.1. Зачем
Список задач может содержать 1000 записей. Отдавать их все за раз — убивать производительность и трафик.
11.2. Offset-based пагинация (page + limit)
GET /api/v1/tasks?page=2&limit=10
Ответ:
{
"data": [ ...10 задач... ],
"pagination": {
"page": 2,
"limit": 10,
"total": 150,
"total_pages": 15
}
}
Проблема: Если между запросами кто-то добавил задачу — пагинация «съедет» (строка 15 может появиться на 2 страницах подряд).
11.3. Cursor-based пагинация
GET /api/v1/tasks?cursor=MTY4NTIzNjQwMA==&limit=10
cursor — это base64-закодированное значение (например, created_at последней задачи на предыдущей странице). Сервер ищет задачи с created_at > decoded(cursor).
Плюс: Стабильная пагинация — новые записи не сдвигают страницы. Минус: Нельзя перейти на страницу 5 (нужно последовательно листать).
Выбор для аналитика:
- Если нужна произвольная навигация («перейти на страницу 5») → offset-based
- Если нужна стабильность и бесконечная лента (как в соцсетях) → cursor-based
Вопросы для самопроверки
Базовый уровень
- Какие шесть принципов REST вы знаете? Объясните Stateless на примере с токеном.
- Какие HTTP-методы используются в REST? Какие из них идемпотентны, а какие безопасны?
- Чем PUT отличается от PATCH? Что произойдет с незаданными полями при PUT? Почему?
- Какие категории HTTP-статус-кодов (1xx, 2xx, 3xx, 4xx, 5xx) вы знаете?
- В чём разница между 401 Unauthorized и 403 Forbidden? Приведите примеры.
Продвинутый уровень
- PUT vs PATCH катастрофа: Клиент отправил
PUT /products/42с единственным полемpriceв теле. Что произойдет сdescription,image_url,category_id? Какие бизнес-последствия? - Content Negotiation: Клиент отправил
Accept: application/xml, но сервер умеет только JSON. Какой статус-код вернёт сервер? Как это исправить на стороне клиента? - Content-Type: Что произойдет, если клиент отправит
Content-Type: application/json, а в теле — невалидный JSON (пропущена запятая)? - Выбор метода: Какой метод использовать, чтобы:
- Увеличить счётчик просмотров товара на 1 (на серверной стороне)?
- Создать задачу (id не знаем)?
- Полностью восстановить задачу из бэкапа (все поля известны)?
- Accept q-factor: Что означает
Accept: application/json; q=1.0, text/html; q=0.5? Какой формат сервер выберет, если умеет и JSON, и HTML?
Практическое задание
Задание 1. Определите метод и статус-код (1 балл)
Для каждого сценария укажите HTTP-метод и корректный статус-код при успехе (если в задаче не указана ошибка — предполагаем успех):
| № | Сценарий | Метод | Статус (успех) |
|---|---|---|---|
| 1 | Получить список пользователей | ||
| 2 | Создать нового пользователя | ||
| 3 | Получить пользователя по ID=15 | ||
| 4 | Обновить только email пользователя (id=15) | ||
| 5 | Удалить пользователя (id=15) | ||
| 6 | Изменить статус задачи (PATCH → Done) | ||
| 7 | Запросить несуществующую задачу (GET /tasks/999) | GET | ? |
| 8 | Создать задачу без названия (POST, валидация не прошла) | POST | ? |
| 9 | Получить список задач без токена | GET | ? |
| 10 | PUT вместо PATCH: клиент отправил PUT /products/42 с одним полем price. Сервер затер остальные поля в NULL. Это ошибка клиента или сервера? |
PUT | ? |
Задание 2. Спроектируйте REST API для Task Manager (1,5 балла)
Для системы управления задачами спроектируйте набор эндпоинтов.
Требования:
- CRUD для задач (Task) — все 4 операции
- CRUD для комментариев (Comment) — все 4 операции
- Смена статуса задачи (отдельный эндпоинт — PATCH со статусом)
- Получение списка задач с фильтрацией по
status,priority,assignee_id - Получение комментариев по конкретной задаче
- Управление пользователями (администратор: CRUD)
- Получение списка участников проекта
Заполните таблицу (минимум 10 строк):
| Метод | URL | Описание | Статус (успех) | Тело запроса |
|---|---|---|---|---|
| GET | /tasks | Список задач | 200 | — |
| POST | /tasks | Создать задачу | 201 | { title, description, ... } |
| GET | /tasks/{id} | Получить задачу по ID | 200 | — |
| PUT | /tasks/{id} | Полностью заменить задачу | 200 | { all fields } |
| PATCH | /tasks/{id} | Частично изменить задачу | 200 | { changed fields } |
| DELETE | /tasks/{id} | Удалить задачу | 204 | — |
| ... | ... | ... | ... | ... |
Дополнительное требование: Для каждого эндпоинта укажите, какие статус-коды ошибок могут быть (минимум 2 на эндпоинт). Например:
GET /tasks/{id}
Успех: 200
Ошибки: 404 (задача не найдена), 401 (не авторизован)
Задание 3. Анализ ошибки: PUT vs PATCH (1 балл)
Ситуация: Вы — системный аналитик. Разработчик написал API-эндпоинт для обновления товара:
PUT /api/v1/products/{id}
Content-Type: application/json
{
"id": 42,
"title": "Новое название",
"price": 9900
}
Проблема: После вызова этого эндпоинта у товара исчезли description, image_url, category_id, weight — все стали NULL.
Вопросы:
- (0,3 балла) Кто допустил ошибку — разработчик API (сервер) или клиент (frontend)? Почему?
- (0,3 балла) Как нужно было правильно спроектировать эндпоинт для частичного обновления?
- (0,2 балла) Напишите три варианта исправления ситуации:
- Вариант А: что должен делать клиент (изменить метод/тело запроса)?
- Вариант Б: что должен сделать сервер (изменить обработку)?
- Вариант В: как можно было предотвратить на уровне контракта (OpenAPI)?
- (0,2 балла) Представьте, что вы проверяете API-контракт на Code Review. Какие конкретные формулировки вы добавите в документацию, чтобы такая ошибка не повторилась?
Задание 4. Content Negotiation (0,5 балла)
Ситуация: Интеграция между системой А (Java, умеет XML) и системой Б (.NET, умеет JSON и XML). Система А отправляет запрос:
GET /api/v1/orders/42
без заголовка Accept. Система Б по умолчанию отвечает в JSON.
Проблема: Система А не может распарсить JSON.
Вопросы:
- (0,1 балла) Почему система Б выбрала JSON, а не XML?
- (0,2 балла) Как должен выглядеть правильный запрос от системы А, чтобы получить XML?
- (0,2 балла) Если бы система А отправила
Accept: application/xml, но система Б вдруг перестала поддерживать XML (только JSON) — какой статус-код она должна вернуть?
Дополнительные материалы
- Книга: Roy Fielding — «Architectural Styles and the Design of Network-based Software Architectures» (диссертация, введение REST)
- Книга: «REST API Design Rulebook» — Марк Массé, короткое практическое руководство
- Стандарт: HTTP Semantics (RFC 9110) — официальная спецификация HTTP
- Стандарт: JSON Merge Patch (RFC 7396) — формат PATCH
- Стандарт: JSON Patch (RFC 6902) — операции для PATCH
- Стандарт: Problem Details for HTTP APIs (RFC 7807) — формат ошибок
- Инструмент: Postman — тестирование REST API
- Инструмент: httpbin.org — тестовый сервер для тренировки запросов
- Статья: «RESTful API Design: 13 Best Practices» — vinaysahni.com
- Статья: «PUT vs PATCH: When to Use Which» — разбор на realworldapi.com
- Инструмент: Swagger Editor (editor.swagger.io) — написание OpenAPI-спецификации