Основы REST API (методы, статус-коды)

Урок 1 из 3

Урок 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. Бизнес-последствия (цепочка катастрофы)

  1. Менеджер заметил, что цена стала 9900 (всё верно)
  2. Через час клиенты видят товар без названия, без картинки, без описания
  3. Отдел маркетинга получает жалобы: «У вас товар без названия»
  4. Администратор идёт в админку, видит NULL в полях
  5. Выясняется, что все товары, которые редактировали через новую форму, потеряли часть данных
  6. Если данные не восстанавливаются из бэкапа — потери необратимы

Оценка ущерба:

  • 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. Как аналитик может предотвратить эту катастрофу

  1. В спецификации API всегда указывайте: «Для частичного обновления используйте PATCH с Partial Update Body (JSON Merge Patch)». Не пишите «PUT для обновления» — разработчик может сделать PUT там, где нужен PATCH.

  2. Документируйте поля обязательные для PUT: Если метод PUT — перечислите ВСЕ поля, которые клиент ОБЯЗАН отправить. Укажите: «Отсутствующие поля будут сброшены в NULL».

  3. Разделяйте эндпоинты: Если нужно только сменить статус — спроектируйте отдельный эндпоинт:

PATCH /tasks/42/status
Content-Type: application/json

{ "status": "Done" }

Или более RESTful:

POST /tasks/42/change-status
Content-Type: application/json

{ "status": "Done" }
  1. В контракте (OpenAPI) опишите разницу:

    • PUT /tasks/{id} — полная перезапись задачи, все поля обязательны
    • PATCH /tasks/{id} — частичное обновление, передаются только изменяемые поля
  2. Проверяйте на 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.

Пошагово:

  1. Клиент отправляет запрос с Accept: application/json; q=1.0, application/xml; q=0.8
  2. Сервер проверяет: какие форматы ответа он умеет генерировать (например, JSON и XML)
  3. Сервер смотрит на приоритет (q — quality factor): JSON с q=1.0 (максимальный), XML с q=0.8 (ниже)
  4. Сервер выбирает JSON (высший приоритет)
  5. Сервер устанавливает Content-Type: application/json в ответе
  6. Сервер отправляет тело в 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 по умолчанию.

Правильное поведение:

  1. Система А отправляет Accept: application/xml
  2. Система Б проверяет Accept, видит application/xml, умеет в XML
  3. Система Б возвращает Content-Type: application/xml
  4. ✅ Интеграция работает

Что делает аналитик: В контракте интеграции прописывает: «Система А отправляет заголовок 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

Механизм:

  1. POST /auth/login с email+password → сервер возвращает JWT-токен
  2. Клиент сохраняет токен и шлёт его в каждом запросе
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

Вопросы для самопроверки

Базовый уровень

  1. Какие шесть принципов REST вы знаете? Объясните Stateless на примере с токеном.
  2. Какие HTTP-методы используются в REST? Какие из них идемпотентны, а какие безопасны?
  3. Чем PUT отличается от PATCH? Что произойдет с незаданными полями при PUT? Почему?
  4. Какие категории HTTP-статус-кодов (1xx, 2xx, 3xx, 4xx, 5xx) вы знаете?
  5. В чём разница между 401 Unauthorized и 403 Forbidden? Приведите примеры.

Продвинутый уровень

  1. PUT vs PATCH катастрофа: Клиент отправил PUT /products/42 с единственным полем price в теле. Что произойдет с description, image_url, category_id? Какие бизнес-последствия?
  2. Content Negotiation: Клиент отправил Accept: application/xml, но сервер умеет только JSON. Какой статус-код вернёт сервер? Как это исправить на стороне клиента?
  3. Content-Type: Что произойдет, если клиент отправит Content-Type: application/json, а в теле — невалидный JSON (пропущена запятая)?
  4. Выбор метода: Какой метод использовать, чтобы:
    • Увеличить счётчик просмотров товара на 1 (на серверной стороне)?
    • Создать задачу (id не знаем)?
    • Полностью восстановить задачу из бэкапа (все поля известны)?
  5. 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.

Вопросы:

  1. (0,3 балла) Кто допустил ошибку — разработчик API (сервер) или клиент (frontend)? Почему?
  2. (0,3 балла) Как нужно было правильно спроектировать эндпоинт для частичного обновления?
  3. (0,2 балла) Напишите три варианта исправления ситуации:
    • Вариант А: что должен делать клиент (изменить метод/тело запроса)?
    • Вариант Б: что должен сделать сервер (изменить обработку)?
    • Вариант В: как можно было предотвратить на уровне контракта (OpenAPI)?
  4. (0,2 балла) Представьте, что вы проверяете API-контракт на Code Review. Какие конкретные формулировки вы добавите в документацию, чтобы такая ошибка не повторилась?

Задание 4. Content Negotiation (0,5 балла)

Ситуация: Интеграция между системой А (Java, умеет XML) и системой Б (.NET, умеет JSON и XML). Система А отправляет запрос:

GET /api/v1/orders/42

без заголовка Accept. Система Б по умолчанию отвечает в JSON.

Проблема: Система А не может распарсить JSON.

Вопросы:

  1. (0,1 балла) Почему система Б выбрала JSON, а не XML?
  2. (0,2 балла) Как должен выглядеть правильный запрос от системы А, чтобы получить XML?
  3. (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-спецификации

📚 Материалы модуля

🖼️ Схема и инфографика

🎬 Видео-лекция

🎬 API и интеграции

📄 Дополнительные материалы (PDF)

📄API Integration Blueprints
Скачать
Спросить ИИ