Учебник веб-разработки
Разделы учебника
На этой странице

VI. HTTP, Next.js и кеширование

HTTP: ресурс, представление и результат запроса

Оглавление · Кеширование · API проекта

Задача: прочитать обмен клиент/сервер и определить намерение операции, результат и границу доверия. Нужны Promise, JSON и runtime-валидация.

Ресурс не равен строке JSON

Ресурс — то, к чему обращаются; представление — передаваемое описание его состояния. URL идентифицирует адрес обращения, метод задаёт намерение, заголовки — метаданные, тело — содержимое. Content-Type описывает формат тела, Accept — предпочтения клиента. HTTP/1.1, HTTP/2 и HTTP/3 меняют передачу сообщений, сохраняя общую семантику. HTTP Semantics.

Рассмотрим учебный адрес /api/articles/sample. Это будущий пример контракта, такого обработчика в Atmanki нет. Одна статья может иметь HTML для читателя и JSON для клиента; ни одно представление не обязано совпадать со строкой БД. Публичный JSON не должен включать редакторские поля только потому, что они есть в CMS.

GET /api/articles/sample HTTP/1.1
Host: example.test
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "article-v7"

{"slug":"sample","title":"Учебная статья"}

Это текстовая иллюстрация, не побайтовый HTTP-пакет с рассчитанной длиной. Настоящий клиент и сервер формируют служебные поля и кодирование.

Метод выражает намерение

МетодНамерениеБезопасныйИдемпотентный по семантике
GETПолучить представлениеДаДа
HEADКак GET, без тела ответаДаДа
POSTОбработать содержимое по правилам ресурсаНетНе обязательно
PUTСоздать/заменить состояние указанного ресурсаНетДа
DELETEУдалить связь с ресурсомНетДа
PATCHПрименить описанные измененияНетНе обязательно

Безопасность здесь означает отсутствие запрошенного изменения состояния; логи возможны. Идемпотентность относится к предполагаемому эффекту повторов, а не одинаковым ответам. Методы. PATCH может быть спроектирован идемпотентным, но не обладает этим свойством по умолчанию. Формат patch-документа определяет смысл изменения. RFC 5789.

В учебном редакторе «установить заголовок X» и «добавить единицу к просмотрам» дают разные переходы. Второй DELETE может сообщить, что ресурс уже отсутствует, хотя требуемое конечное состояние сохранено. GET /delete?id=sample нарушает ожидания метода: ссылку могут открыть предварительная загрузка или робот.

Кешируемость — отдельный вопрос: её нельзя вывести только из идемпотентности. Правила HTTP-кеша разобраны в предыдущей главе.

Статус и прикладной результат

СтатусСмысл для клиента
200Успешное выполнение; формат результата задан контрактом
201Создан ресурс; Location может указать его адрес
202Принято в обработку; завершение ещё не подтверждено
204Успех без тела ответа
400 / 415 / 422Неверный запрос / неподдерживаемый формат / необрабатываемое содержимое
401 / 403Нужна аутентификация / доступ запрещён
404Ресурс не найден либо его существование не раскрывается
409 / 412Конфликт состояния / не выполнено предусловие
429Превышен лимит запросов
500 / 503Внутренняя ошибка / временная недоступность

Это ориентиры, а не универсальная таблица повторов. Справочник статусов. Конкретный контракт уточняет тело ошибки и допустимое действие клиента. У 204 нельзя безусловно вызывать response.json(); у 202 нужен отдельный способ узнать итог. JSON с полем ok: false не превращает HTTP 200 в HTTP-ошибку.

Исходник схемы
flowchart TD
  Request["Запрос"] --> Transport{"Получен HTTP-ответ?"}
  Transport -->|Нет| Unknown["Исход операции может быть неизвестен"]
  Transport -->|Да| Status["Статус и заголовки"]
  Status --> Decode["Тело по контракту, если оно предусмотрено"]
  Decode --> Result["Прикладной результат и ошибки"]

Ошибка сети, HTTP-ошибка, неверный формат и прикладной отказ — разные случаи. Не маскируйте всё одним «не найдено» и не показывайте клиенту внутренний stack trace. Для машиночитаемых ошибок можно выбрать Problem Details: тип проблемы, статус, заголовок и детали. Это формат, а не готовая классификация бизнес-ошибок. RFC 9457.

Условное обновление

Два редактора читают версию v7. Первый сохраняет новую v8; второй отправляет изменение с If-Match: "article-v7". При несовпадении текущего валидатора сервер отклоняет предусловие, вместо молчаливой потери первого изменения. If-Match.

Сравнение версии и запись должны быть согласованы в хранилище: проверка в одном чтении и безусловная запись позже оставляют гонку. Идемпотентный PUT сам по себе не защищает от конкурирующего редактора. В Atmanki такой endpoint и механизм If-Match пока не реализованы.

Где проверяется доверие

Аутентификация отвечает «кто обращается», авторизация — «что ему разрешено». Схема Zod отвечает на другой вопрос: подходит ли значение заявленной форме. Проверка slug: string не разрешает чтение любой закрытой записи.

В проекте webhook проверяет Bearer secret на сервере, затем JSON и общую схему. Контекст tRPC пока {}: будущая операция записи потребует отдельной модели доступа. Cookies, CORS и CSRF подробно разобраны в главе о браузерных границах; не считайте наличие Origin или типизированного клиента авторизацией.

Практика на существующем коде

Сравните health, content-health и webhook. Первый отвечает без CMS-чтения; второй возвращает 200/503 и no-store; третий различает отсутствие настройки, неверный доступ, неверный вход и ошибку инвалидирования. Успех webhook подтверждает приём и обработку уведомления, а не обновление каждого открытого окна.

На локальном сайте выполните только чтение:

curl --include http://localhost:3000/api/health
curl --include http://localhost:3000/api/content-health

Запишите статус, Content-Type, Cache-Control и смысл тела. Сопоставьте ответ с кодом, не делая вывода о PostgreSQL из одного /api/health. Затем спроектируйте на бумаге условное изменение учебной статьи и ответ на конфликт редакторов. Это упражнение, не новая production-операция.