Практика 1: сервис REST API (бэкенд задач)
Одна глава сшивает первые четыре тома: REST-сервис с персистентностью, аутентификацией, пагинацией и горячей перезагрузкой — от пустого каталога до принятой приёмки.
Обзор
45.1 Цель проекта
Собираем бэкенд «задач» (любой ресурс аналогично):
| Возможность | Эндпоинты | Тома |
|---|---|---|
| CRUD ресурса | GET/POST /api/v1/todos, GET/PUT/PATCH/DELETE /api/v1/todos/{id} | том 3 (14–18) |
| Персистентность | SQLite, без потерь при смене поколений | гл. 32 + гл. 28 |
| Пагинация и фильтры | ?page=&size=&done= | гл. 18 |
| Токен-аутентификация | заголовок X-Api-Token | гл. 27/39 |
| Эксплуатационная поверхность | /health, /stats, /admin/reload | гл. 26–27/38 |
| Форма развёртывания | один каталог + systemd | гл. 42 |
45.2 Фундамент (30 минут)
- Скелет: каталоги по конвенциям гл. 05 (modules/ route_http/ db/ wwwroot/); main.c оркестрирует include; xs.json — минимальная конфигурация по гл. 07/08
- Три модуля: modules/http.h (семейство Reply из гл. 15), modules/db.h (DB_Init/Unit из гл. 32), modules/route.h (две таблицы гл. 17)
- Таблица:
todo(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER DEFAULT 0, created INTEGER)
45.3 CRUD и соглашения
/* POST /api/v1/todos: читаем JSON (гл. 16+18) → вставка с привязкой (гл. 32) */
static void Handle_Create(XS_HttpReq* pReq, const RouteParamHTTP* a, uint32 n)
{
xvalue* pBody = ReadRequestJSON(pReq); /* обёртка: body→parse */
if ( pBody == NULL ) return (void)ApiError(pReq, 400, "bad_json");
xvalue* pTitle = xrtValueObjectGet(pBody, "title");
if ( pTitle == NULL || !IsNonEmptyString(pTitle) )
return (void)(xrtValueRelease(pBody),
ApiError(pReq, 422, "field_required:title"));
...
int64 iId = DB_InsertTodo(ViewOf(pTitle));
xrtValueRelease(pBody);
ReplyItemJSON(pReq, 201, iId); /* 201 + новый ресурс */
}
/* PUT/PATCH /api/v1/todos/{id}: параметры пути + частичное обновление */
/* трёхчастное соглашение об ошибках: 404 (нет) / 422 (поле) / 200 (успех) */
Соглашение об ошибках — заранее (ключевая привычка API-строителя): 400 битый JSON, 401 токен, 404 ресурс, 405 метод (маршрутизация сама), 413 превышение (драйвер сам), 422 семантика — у каждого машиночитаемое поле code. PUT отсутствующего ресурса: 404 или upsert? Выберите одно и впишите в документацию; двух не бывает.
45.4 Пагинация и аутентификация
- Пагинация: список
SELECT ... LIMIT ? OFFSET ?+ тоталSELECT COUNT(*); ответ{"total":N,"page":P,"data":[...]}— верхний clamp (например size ≤ 100) обязателен как линия обороны - Аутентификационный слой: один модульный
CheckToken(pReq)(сравнение за постоянное время), первой строкой каждого обработчика /api/*; токен в ручке Custom (строгое чтение гл. 09) - Учения перезагрузки: сменить правило сортировки списка → reload → проверить (а) запросы в полёте без ошибок (б) данные целы (поколения гл. 32) (в) счётчик непрерывен между поколениями (Swap несёт total — выпускной экзамен гл. 28)
45.5 Лист приёмки
| Измерение | Стандарт | Основание |
|---|---|---|
| Функции | CRUD/пагинация/фильтры/аутентификация — всё проходит | эта глава |
| Протокол | HEAD поддержан (регистрация GET|HEAD), 405+Allow корректны | гл. 15/17 |
| Персистентность | без потерь при рестарте и смене поколений | гл. 32 |
| Устойчивость | смена поколений под нагрузкой без отказов; четыре состояния лимитов верны | гл. 28/09 |
| Наблюдаемость | /health /stats работают; логи запросов с ID | гл. 43 |
| Развёртывание | юнит systemd + окно корректной остановки | гл. 42 |
Частые ошибки
- Симптом
- Унификация формата ошибок в конце требует правки всех обработчиков.
- Причина
- Соглашение об ошибках — конституция API; фиксируйте первым шагом 45.3 (помощник ApiError + таблица code).
- Симптом
- Один ?size=1000000 — и сервис уходит в глубокие сканы пагинации.
- Причина
- Параметры пагинации — ввод клиента; верхний clamp и умолчание — часть интерфейса.
Упражнения
Пройдите все точки приёмки 45.2–45.5.
Добавьте поле updated и оптимистическую блокировку If-Match (409 при конфликте).
Реализуйте /api/openapi.json: опишите эндпоинты через xvalue (документация, ведомая таблицей маршрутов).
Итоги
| Тезис | Вывод |
|---|---|
| Поток | скелет→три модуля→соглашение об ошибках→CRUD→пагинация и аутентификация→приёмка |
| Ключевые привычки | соглашение об ошибках первым; clamp параметров; единый вход аутентификации |
| Знак выпуска | смена поколений под нагрузкой без отказов + данные целы + счётчики непрерывны |
- xrt · value и json — главные инструменты проекта