HTTP и REST API: клиент-серверное взаимодействие
Структура HTTP-запроса и ответа, методы GET и POST, коды состояния, принципы REST, проектирование эндпоинтов, аутентификация по токену и документирование API.
Практически любая современная курсовая по разработке содержит клиент и сервер, общающиеся по HTTP. Разберём, как устроен этот обмен и как спроектировать API, за которое не будет стыдно на защите.
Структура запроса и ответа
POST /api/orders HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
Content-Length: 58
{"productId": 42, "quantity": 3, "comment": "срочно"}
--- ответ ---
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/orders/1024
{"id": 1024, "status": "new", "createdAt": "2026-08-17T10:15:00Z"}
Методы
| Метод | Назначение | Идемпотентен | Тело запроса |
|---|---|---|---|
| GET | Получить данные | Да | Нет |
| POST | Создать ресурс | Нет | Да |
| PUT | Заменить целиком | Да | Да |
| PATCH | Изменить часть полей | Нет | Да |
| DELETE | Удалить | Да | Обычно нет |
Коды состояния
| Код | Значение | Когда |
|---|---|---|
| 200 OK | Успех | GET, успешный PATCH |
| 201 Created | Ресурс создан | POST, в заголовке Location — адрес нового ресурса |
| 204 No Content | Успех без тела | DELETE |
| 301 / 302 | Перенаправление | Смена адреса, постоянная и временная |
| 400 Bad Request | Ошибка в данных запроса | Не прошла валидация |
| 401 Unauthorized | Не аутентифицирован | Нет или истёк токен |
| 403 Forbidden | Нет прав | Аутентифицирован, но доступ запрещён |
| 404 Not Found | Ресурс не найден | Неверный id или путь |
| 409 Conflict | Конфликт состояния | Дубликат, нарушение уникальности |
| 422 | Данные корректны по формату, но не по смыслу | Часто вместо 400 |
| 429 | Слишком много запросов | Срабатывание ограничителя |
| 500 Internal Server Error | Ошибка на сервере | Необработанное исключение |
Разница между 401 и 403 — популярный вопрос: первый означает «мы не знаем, кто вы», второй — «знаем, но вам нельзя». Возврат 200 с текстом ошибки в теле считается плохой практикой: клиент не может отличить успех от сбоя, не разбирая содержимое.
Принципы REST
- Ресурс — существительное во множественном числе: /api/orders, а не /api/getOrders.
- Действие выражается методом, а не адресом: DELETE /api/orders/5 вместо GET /api/deleteOrder?id=5.
- Иерархия через вложенность: /api/orders/5/items.
- Фильтрация, сортировка и постраничность — параметрами строки запроса: ?status=new&sort=-createdAt&page=2&limit=20.
- Отсутствие состояния: каждый запрос самодостаточен, сервер не хранит сессию между вызовами.
- Единообразие: одинаковый формат ошибок и структура ответов по всему API.
Хорошо Плохо
GET /api/products GET /api/getAllProducts
GET /api/products/42 GET /api/product?id=42
POST /api/products POST /api/createProduct
PATCH /api/products/42 POST /api/updateProduct
DELETE /api/products/42 GET /api/deleteProduct?id=42
GET /api/products?category=r&page=2 GET /api/productsByCategoryPage2
Сервер на Express
const express = require('express');
const app = express();
app.use(express.json());
const orders = [];
app.get('/api/orders', (req, res) => {
const { status, page = 1, limit = 20 } = req.query;
let result = status ? orders.filter(o => o.status === status) : orders;
const start = (page - 1) * limit;
res.json({ total: result.length, items: result.slice(start, start + Number(limit)) });
});
app.get('/api/orders/:id', (req, res) => {
const order = orders.find(o => o.id === Number(req.params.id));
if (!order) return res.status(404).json({ error: 'Заказ не найден' });
res.json(order);
});
app.post('/api/orders', (req, res) => {
const { productId, quantity } = req.body;
if (!productId || !Number.isInteger(quantity) || quantity < 1) {
return res.status(400).json({ error: 'Некорректные данные', fields: ['productId', 'quantity'] });
}
const order = { id: orders.length + 1, productId, quantity, status: 'new' };
orders.push(order);
res.status(201).location(`/api/orders/${order.id}`).json(order);
});
app.listen(3000);
Аутентификация
| Способ | Как работает | Где применяют |
|---|---|---|
| Сессия и cookie | Сервер хранит сессию, клиент — идентификатор | Классические веб-приложения |
| Токен JWT | Подписанный токен со сведениями о пользователе | SPA, мобильные приложения |
| API-ключ | Постоянный ключ в заголовке | Межсервисное взаимодействие |
| OAuth 2.0 | Доступ через сторонний провайдер | Вход через Google, ВК |
Документирование и тестирование
- Опишите API в формате OpenAPI (Swagger) — получите наглядную документацию и страницу для ручных проверок.
- В отчёт включите таблицу эндпоинтов: метод, путь, назначение, коды ответов.
- Приложите примеры запросов и ответов — по одному на каждый метод.
- Проверьте API через Postman или curl и вставьте снимки экрана.
- Опишите обработку ошибок: единый формат тела ответа при сбое.
curl -X POST http://localhost:3000/api/orders \
-H 'Content-Type: application/json' \
-d '{"productId": 42, "quantity": 3}' -i
curl 'http://localhost:3000/api/orders?status=new&page=1&limit=10'
Частые вопросы
Чем REST отличается от SOAP?
REST опирается на возможности самого HTTP и обменивается обычно JSON, SOAP — протокол поверх HTTP со строгой схемой XML. REST проще и легче, SOAP встречается в корпоративных и банковских интеграциях, где нужна жёсткая формализация.
Обязательно ли соблюдать все правила REST?
Полное соответствие требуется редко. Важнее последовательность: если во всём API действия выражены методами и одинаково устроены ошибки, работать с ним удобно. Смесь стилей вызывает больше замечаний, чем сознательное отклонение.
Что возвращать при удалении несуществующего объекта?
Строго — 404. Но многие API отвечают 204, считая операцию идемпотентной: результат в любом случае «объекта нет». Выберите вариант, зафиксируйте его в документации и придерживайтесь везде.