Learning Platform
Глоссарий Troubleshooting
Урок 11.06 · 18 мин
Начальный
RESTAPI designHTTP methodsArchitecture

REST поверх HTTP — архитектурный стиль, а не протокол

В этом курсе мы изучаем HTTP как сетевой протокол: байты, заголовки, версии. Но 90% случаев, когда вы используете HTTP в работе, — это вызовы API. И почти каждый разговор про API упирается в слово «REST». «Сделай мне REST API», «у нас REST endpoint», «это не REST, это RPC».

Этот урок — обзор REST с фокусом «что это вообще такое и при чём здесь HTTP». Глубоко мы REST не разбираем — для этого есть отдельный курс rest-api-fundamentals, где обсуждаются ресурсы, идентификация, версионирование, pagination, HATEOAS, ошибки, security, и десятки других тем дизайна API. Здесь же мы посмотрим на REST с network-perspective: как идиоматичный REST использует HTTP-методы, статус-коды, заголовки — то, что вы уже знаете из этого модуля.

Если ваша работа — интеграции с API, ETL из чужих endpoint’ов, проектирование собственных сервисов — идите в rest-api-fundamentals обязательно. Здесь — быстрый overview, чтобы вы понимали, как HTTP используется в API-мире.


Что такое REST на самом деле

REST — это архитектурный стиль, описанный Роем Филдингом в его диссертации 2000 года. Не протокол, не спецификация, не RFC — набор архитектурных принципов (constraints), которые в совокупности дают желаемые свойства распределённой системы.

REST constraints
Client-ServerРазделение между клиентом и сервером. Каждая сторона эволюционирует независимо. Базовый принцип любой сетевой архитектуры
StatelessКаждый request содержит всю информацию для своей обработки. Сервер не хранит client context между запросами. Это даёт horizontal scaling
CacheableОтветы должны явно говорить, кэшируемы ли они. Кэширование снижает latency и нагрузку. Через HTTP это Cache-Control
Uniform interfaceЕдиный interface между компонентами. Использует HTTP-методы и URI как стандартные операции. Идентифицируется resource, действие -- метод
Layered systemКлиент не знает, общается ли с конечным сервером или с прокси/балансировщиком. Можно вставлять промежуточные слои -- caching, security, transformation
Code on demand (опц.)Сервер может отправлять код, выполняющийся на клиенте (JavaScript). Опциональный constraint -- большинство REST API его не используют

Из всех constraints на практике людей волнует один — uniform interface. Из этого constraint следует, что REST API:

  1. Ресурс-ориентирован. Каждый URL обозначает ресурс. /users/42 — пользователь 42, /orders/100/items — список items заказа 100.
  2. Использует HTTP-методы как глаголы. GET читает, POST создаёт, PUT/PATCH обновляет, DELETE удаляет.
  3. Использует статус-коды по семантике. 200 OK, 201 Created, 404 Not Found, 422 Unprocessable Entity.
  4. Возвращает медиа-типы. JSON, XML, иногда HTML. С Content-Type.

Идиоматичный REST: пример CRUD для users

Возьмём API управления пользователями. Идиоматичный REST это:

# Получить список пользователей
GET /users HTTP/1.1
Accept: application/json

# Ответ
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "list-version-42"

{"users": [{"id": 1, ...}, {"id": 2, ...}], "total": 100}
# Получить одного пользователя
GET /users/42 HTTP/1.1
Accept: application/json

# Ответ
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "user-42-v3"

{"id": 42, "name": "Linus", "email": "[email protected]"}
# Создать нового пользователя
POST /users HTTP/1.1
Content-Type: application/json

{"name": "Ada", "email": "[email protected]"}

# Ответ
HTTP/1.1 201 Created
Location: /users/43
Content-Type: application/json

{"id": 43, "name": "Ada", "email": "[email protected]"}
# Полностью заменить пользователя
PUT /users/42 HTTP/1.1
Content-Type: application/json
If-Match: "user-42-v3"

{"id": 42, "name": "Linus B Torvalds", "email": "[email protected]"}

# Ответ
HTTP/1.1 200 OK
ETag: "user-42-v4"
Content-Type: application/json

{"id": 42, "name": "Linus B Torvalds", ...}
# Частичное обновление
PATCH /users/42 HTTP/1.1
Content-Type: application/json

{"name": "Linus Benedict Torvalds"}

# Ответ
HTTP/1.1 200 OK
ETag: "user-42-v5"

{"id": 42, ...}
# Удалить
DELETE /users/42 HTTP/1.1

# Ответ
HTTP/1.1 204 No Content

Заметьте паттерн: URL обозначает ресурс, метод — действие. URL никогда не содержит глаголы (/users/42/delete — НЕ REST; DELETE /users/42 — REST).


Статус-коды в REST: правильные коды для правильных ситуаций

Идиоматичный REST API использует широкую палитру статус-кодов:

Status codes по операциям REST
GET success200 OK -- стандартный ответ с телом. Содержит ресурс
POST created201 Created -- ресурс создан. Должен включать Location header с URI нового ресурса. Тело -- созданный ресурс
DELETE / no body204 No Content -- успех без тела. Часто используется после DELETE или PUT без необходимости вернуть ресурс
Bad input400 Bad Request -- запрос синтаксически некорректен. Например, невалидный JSON или missing required field. Клиент не должен ретраить без исправления
Auth needed401 Unauthorized -- credentials отсутствуют или невалидны. Клиент должен залогиниться/обновить токен
No permission403 Forbidden -- credentials есть, но нет прав на эту операцию. Логин не поможет, нужен другой user или измененные права
Not found404 Not Found -- ресурс не существует. Используется и для несуществующих ID, и иногда для скрытия 403 (security through obscurity)
Validation422 Unprocessable Entity -- запрос синтаксически OK (валидный JSON), но семантически нет (email невалидный, возраст отрицательный). Часто содержит детали по полям
Rate limit429 Too Many Requests -- слишком много запросов. Содержит Retry-After header с секундами до повтора. Клиент должен уважать
Conflict409 Conflict -- запрос противоречит текущему состоянию ресурса. Например, попытка создать уже существующий unique resource
Precondition412 Precondition Failed -- условие If-Match / If-Unmodified-Since не выполнилось. Optimistic locking при concurrent update
Server error500 Internal Server Error -- бэкенд упал. Клиент может ретраить (с backoff), но не сразу

Полезные тонкости:

  • 201 Created vs 200 OK для POST. Если POST создал ресурс — 201, с Location header. Если POST выполнил действие без создания (например, /users/42/send-email) — 200 OK или 204.
  • 404 vs 410. 404 — может не существует, может вы не знаете. 410 — точно когда-то существовало, но удалено.
  • 404 vs 403. Некоторые системы возвращают 404 вместо 403 — чтобы атакующий не мог даже узнать о существовании ресурса.

Глубже про дизайн ошибок и pagination — в rest-api-fundamentals.


REST vs RPC: разница

RPC (Remote Procedure Call) — альтернативный стиль. Вместо ресурсов и методов — функции:

REST:               RPC:
GET /users/42       POST /getUser {"id": 42}
POST /users         POST /createUser {...}
DELETE /users/42    POST /deleteUser {"id": 42}

В RPC URL — это название функции. Метод обычно всегда POST (потому что в RPC мысленная модель — «вызов функции с параметрами»).

Что лучше? Зависит от случая:

  • REST: натурален для CRUD-операций над сущностями. Хорош для public APIs (Stripe, GitHub).
  • RPC: натурален для actions, которые не лезут в CRUD-модель. «Запусти задачу», «отмени операцию», «синхронизируй».
  • gRPC: современная реализация RPC поверх HTTP/2 с бинарным форматом (protobuf). Очень быстро, типизировано, отлично для internal services.

В реальности почти все «REST APIs» — это гибрид. PayPal, Stripe — ресурсо-ориентированные, но имеют action-эндпоинты типа POST /charges/{id}/refund. Это нормально и прагматично.


Ресурсы и URI: какие хорошие, какие плохие

Дизайн URI — большая тема, тут только базовые правила:

Хорошие vs плохие URI
GET /users/42Хорошо: множественное число (users), ID после. Стандартно, читаемо
GET /getUser?id=42Плохо: глагол в URL, RPC-стиль. Для REST используйте path вместо query для resource ID
GET /users/42/ordersХорошо: nested ресурс. Заказы пользователя 42. Читаемо, отражает hierarchy
GET /orders?userId=42Тоже OK: запрос orders с фильтром по user. Когда есть несколько фильтров -- query parameters лучше
DELETE /users/42Хорошо: ресурс + метод. Идиоматично
POST /users/42/deleteПлохо: глагол в URL, неправильный метод. Так делают только если у клиента нет DELETE метода (старые формы)
GET /search?q=linusХорошо: для actions, не подходящих под CRUD-модель, используйте verb-эндпоинты с query parameters
GET /UsersListПлохо: PascalCase, не plural noun. Лучше /users

Правила:

  1. Существительные для ресурсов во множественном числе: /users, /orders, /products.
  2. ID в path для конкретного ресурса: /users/42.
  3. Глагол — HTTP метод, а не URL.
  4. Nested для вложенных ресурсов: /users/42/orders.
  5. Query parameters для фильтров/пагинации: /orders?status=paid&page=2.
  6. Lowercase, kebab-case для multi-word: /user-profiles, не /UserProfiles.

Подробно про дизайн — курс rest-api-fundamentals.


Где REST не идеален

REST — хороший дефолт, но не серебряная пуля.

  1. Сложные queries. Получить «users старше 25, у которых есть orders за последний месяц, с количеством orders > 5» — сложно выразить через ресурсы. Тут лучше GraphQL или специальный search endpoint.

  2. Real-time data. REST — request/response. Для уведомлений или live feed — WebSocket, SSE, gRPC streaming.

  3. Большое количество round-trips. Если для UI нужно 20 разных ресурсов — 20 HTTP-запросов. GraphQL умеет одним запросом.

  4. Внутри microservices. Между сервисами в data center REST с JSON — не самый эффективный. gRPC с protobuf быстрее и более типизирован.

В реальной архитектуре часто гибрид: REST для external public API + gRPC / GraphQL внутри + WebSocket для real-time.


Реальный пример: GitHub API

GitHub API — хороший пример идиоматичного REST. Несколько примеров:

# Получить пользователя
curl https://api.github.com/users/torvalds

# Получить репозиторий
curl https://api.github.com/repos/torvalds/linux

# Список звёзд репо
curl https://api.github.com/repos/torvalds/linux/stargazers

# Создать gist (нужна аутентификация)
curl -X POST https://api.github.com/gists \
  -H "Authorization: Bearer ghp_xxx" \
  -H "Content-Type: application/json" \
  -d '{"description": "test", "files": {"a.txt": {"content": "hello"}}, "public": true}'

# Обновить gist
curl -X PATCH https://api.github.com/gists/{id} \
  -H "Authorization: Bearer ghp_xxx" \
  -d '{"description": "updated"}'

# Удалить gist
curl -X DELETE https://api.github.com/gists/{id} \
  -H "Authorization: Bearer ghp_xxx"

Обратите внимание:

  • URL — ресурсы во множественном числе (/users, /repos, /gists).
  • ID или name в path.
  • Методы соответствуют действиям.
  • Статус-коды семантичны: 200 OK при GET, 201 Created при POST успешной, 404 Not Found для несуществующих, 403 для no permission.
  • ETag присутствует — можно делать conditional requests.

Попробуй сам

# 1. Изучить GitHub API -- классический REST
curl -v https://api.github.com/users/torvalds 2>&1 | grep -E '^[<>]' | head -30

# 2. Посмотреть на ETag и conditional request
ETAG=$(curl -sI https://api.github.com/users/torvalds | grep -i etag | awk '{print $2}' | tr -d '\r')
echo "ETag: $ETAG"
curl -v -H "If-None-Match: $ETAG" https://api.github.com/users/torvalds 2>&1 | grep -E '^[<>]' | head -10
# Должно быть 304 Not Modified

# 3. Попробуй разные статус-коды через httpbin
curl -v https://httpbin.org/status/201 2>&1 | grep '^<'
curl -v https://httpbin.org/status/422 2>&1 | grep '^<'
curl -v https://httpbin.org/status/429 2>&1 | grep '^<'

# 4. POST с JSON -- посмотреть, как сервер парсит
curl -v -X POST https://httpbin.org/anything \
  -H "Content-Type: application/json" \
  -d '{"resource": "user", "action": "create"}' 2>&1 | tail -20

# 5. Find a public REST API на https://github.com/public-apis/public-apis
# Например, открытое API JSONPlaceholder
curl https://jsonplaceholder.typicode.com/posts/1
curl https://jsonplaceholder.typicode.com/users/1
curl -X POST -H "Content-Type: application/json" \
     -d '{"title": "test"}' \
     https://jsonplaceholder.typicode.com/posts

Куда дальше — rest-api-fundamentals

Этот урок был быстрым обзором REST с network-perspective. Если ваша работа — API (интеграции, проектирование, миграции, документация), обязательно пройдите курс rest-api-fundamentals. Там разбирается:

  • Глубокий дизайн URL и ресурсов (nested, polymorphic, hierarchical)
  • Pagination — cursor vs offset, edge cases
  • Versioning — /v1/, header-based, content negotiation
  • Error format — RFC 7807 Problem Details, какие поля давать, как структурировать
  • Authentication patterns — Basic, Bearer, OAuth 2, OIDC, API keys
  • Rate limiting — token bucket, sliding window, как сервер шлёт headers
  • HATEOAS — что это и зачем
  • Webhooks — callbacks, как принимать, security (HMAC signatures)
  • OpenAPI / Swagger — документация и codegen
  • GraphQL и gRPC — альтернативы и trade-offs

Здесь, в networking-fundamentals, мы концентрируемся на сетевом стэке. Дальше идём в TLS, sockets, балансировку.


Что вы должны вынести

  1. REST — архитектурный стиль, не протокол.
  2. Ресурс-ориентированность: URL = noun, метод = verb.
  3. Идиоматичный REST использует широкий спектр HTTP status codes по семантике.
  4. REST vs RPC vs GraphQL — разные стили под разные задачи. Часто гибрид.
  5. URI design: множественные существительные, ID в path, kebab-case, нет глаголов.
  6. Conditional requests + ETag прекрасно работают с REST для concurrency control.
  7. Глубже про дизайн — курс rest-api-fundamentals.

Полный курс REST API: дизайн, pagination, versioning, security, OpenAPI
Проверка знанийKnowledge check
Junior спрашивает: 'мне сказали сделать REST API. У нас будет endpoint, который запускает фоновую задачу обработки видео. Это не CRUD -- у меня нет 'ресурса', который я создаю/читаю/обновляю. Как назвать endpoint и какой метод использовать, чтобы это было идиоматичный REST?'
ОтветAnswer
Это очень частая ситуация -- action, который не лезет в CRUD-модель. Несколько подходов: (1) Создать ресурс 'job/task' -- сама задача обработки является ресурсом. POST /video-processing-jobs создаёт новую задачу обработки, возвращает 202 Accepted (асинхронно принято) + Location: /video-processing-jobs/123 + ID задачи. Дальше клиент через GET /video-processing-jobs/123 опрашивает статус (pending, processing, completed, failed). Это самый чистый REST: задача -- сущность, у неё есть жизненный цикл, методы соответствуют. (2) Sub-resource action: POST /videos/42/process-jobs. Здесь явно видно, что мы создаём jobs к конкретному видео. Тоже хорошо. (3) Verb endpoint -- POST /videos/42/process. Не классический REST, но прагматичный. PayPal, Stripe так делают для действий (POST /charges/123/refund). 202 Accepted в ответе плюс Location для polling. Главное: НЕ POST /processVideo с body {video_id: 42} -- это RPC-стиль, тогда у вас не REST API а RPC под видом REST. Также важно: для долгих операций возвращать 202 Accepted (а не 200 OK), Location header с URI ресурса для polling, и обязательно идемпотентность через Idempotency-Key header -- если клиент дважды нажмёт submit, не запустить обработку дважды. Глубже про async API patterns -- в rest-api-fundamentals.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Какое из следующих утверждений о REST наиболее точно?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6