Перейти к содержанию

Разбор API перед выпуском

По одному примеру трудно понять, согласован ли API целиком. Успешный GET может быть аккуратным, а повтор POST — создавать дубликат; правильно описанный ETag может соседствовать с PUT без атомарной проверки. Перед выпуском нужно пройти реальные намерения клиента через несколько связанных границ.

В этом уроке соберём договор каталога в итоговый редакционный разбор. Результат — вы сможете объяснить, что считается успехом каждого сценария и какие требования ещё предстоит подтвердить реализации. Это не отчёт выполненных тестов: сервер, браузерные сценарии, worker, криптография и команды не запускались.

Сначала фиксируем модель, а не фреймворк

Исходный каталог содержит c001–c004 со знакомыми title, topic и lessons. Публичное чтение курса выбирает JSON либо HTML, список — JSON. Вход изменения содержит три редактируемых поля, а id назначается сервером. Каждый самостоятельный пример начинает этот каталог заново, если внутри статьи явно не описано изменение.

Alice u17 является editor для frontend. Она может работать с c001 и c002, но не с c003 quality и c004 tools. Браузер использует session cookie и CSRF, программный клиент — явный Bearer без сессионной cookie. Две схемы не смешиваются, чтобы сервер не угадывал личность.

Эти определения позволяют читать итоговую карту. Она обозначает области операций, а не полную таблицу всех заголовков или готовый маршрутизатор.

Намерение Операция Основная граница
Прочитать курс GET курса Выбранное публичное представление
Заменить редактируемые поля PUT курса Права и конкретный If-Match
Создать курс POST списка Проверенный вход и ключ намерения
Получить страницу GET списка с cursor Фиксированный снимок и порядок
Прикрепить текст POST files курса Байты, права, лимит и публикация
Получить закрытый файл GET файла Owner и версия выбранных байтов
Запустить экспорт POST exports Принятие одного задания
Прочитать состояние GET exports/id Owner и отдельный фоновой результат

Чтение и изменение проверяют разные вещи

Клиент читает JSON c001 и сохраняет сильный "c001-json-r1". Если он позже выполняет условный GET с подходящим If-None-Match, возможен 304 без тела. Клиент использует сохранённое тело только вместе с соответствующим представлением и метаданными, а не воспринимает отсутствие нового JSON как пустой курс.

HTML c001 имеет другой тег. Vary:Accept не позволяет общему кешу перепутать форматы. Персональный /api/me не добавляется в публичный ответ курса и использует private,no-store. Если появится разрешённый cross-origin editor, Vary учитывает также Origin, а CORS-поля дают браузеру разрешение читать ответ.

Для PUT клиент отправляет конкретный сильный тег JSON и полный вход. Проверка прав, сравнение актуальной версии и фиксация должны иметь атомарную границу. При отсутствии условия выбираем 428, при неподходящей версии — 412. Слабый тег не защищает потерю побайтового обновления так, как требуется этому договору. Условия запросов RFC 9110.

Конкуренция должна оставлять понятный исход

Представим двух редакторских окон Alice. Оба прочитали r1. Первое увеличило lessons до 41 и успешно сохранило новую версию. Второе пытается записать старое значение 40 с прежним условием. Ожидается 412 без применения второй записи, а не молчаливое перетирание новой работы.

После успеха первого PUT ETag не отправляется, поскольку тело входа нормализовано и представление чтения отличается. Новый GET даёт актуальный тег. Если ответ PUT потерялся, повтор со старым If-Match тоже может дать 412: это не доказывает, что первая попытка была неудачной. Клиент читает состояние и разрешает исход.

Нельзя автоматически убрать If-Match после отказа. И нельзя без участия смысла данных отправить старую форму с новым тегом, полученным лишь ради прохождения проверки: тогда защита версии сохранит механическую форму, но пользователь всё равно перезапишет чужое изменение.

Создание связывается с намерением

POST нового frontend-курса разрешён Alice и использует прикладной Idempotency-Key. При потере ответа повтор с тем же subject, методом, путём, ключом и нормализованными данными возвращает прежний созданный id. Иные данные под ключом дают 409. Ротация sid или токена не меняет subject и историю намерения.

first accepted creation -> stored course + stored operation result
response lost          -> client does not know the outcome
same key and payload   -> previous creation result
same key, other data   -> 409, no second creation under that key

Это требование к атомарной реализации, не заявление о проведённом опыте. Запись бизнес-объекта без результата реестра позволила бы повтору создать дубликат. Запись результата до объекта дала бы ложный успех. Двадцатичетырёхчасовой срок ключа ограничивает гарантию; позднее намерение требует отдельного разрешения исхода.

429 регулирует допуск попытки по бюджету, а не меняет ключ. После допустимого ожидания клиент сохраняет прежнюю идентичность операции. Обрыв соединения не равен 429: он не сообщает, что бизнес-изменение не было принято.

Браузерная запись проходит несколько границ

Для cookie-режима сервер проверяет живую сессию, роль и область ресурса, session-bound CSRF token и точный допустимый Origin. SameSite=Lax помогает, но не заменяет эти проверки: доверенный editor находится на другом origin того же сайта. XSS остаётся отдельной угрозой и способен выполнять действия внутри доверенного контекста.

CORS preflight не содержит обычных credentials и не изменяет каталог. Он проверяет запрашиваемые origin, метод и заголовки. Даже удачный OPTIONS не означает, что фактический PUT будет разрешён: его credentials, права, CSRF и версия проверяются независимо. Простой чужой запрос способен дойти до сервера, поэтому отсутствие CORS-разрешения не является доказательством отсутствия побочного эффекта. WHATWG Fetch, CSRF guidance OWASP.

Bearer-клиент явно задаёт токен и исключает session cookie. Механизм ambient-cookie CSRF к нему не применяется в том же виде, однако токен всё равно нуждается в безопасном хранении и передаче. Поддержка альтернативы не делает cookie значением Bearer.

Файл, задание и событие имеют свою атомарность

Загрузка f001 принимает узкий UTF-8 текст до двух МиБ. Ошибка не публикует частичный файл. Успех согласует байты, metadata и сохранённый результат ключа; файловая система и база могут потребовать staging и восстановление, а не одну выдуманную транзакцию.

Range выбирает байты доступного owner-файла. Для двенадцати ASCII-байтов 0–3 дают ABCD, а сильное совпадение If-Range позволяет продолжить прежнюю копию. Несовпадение даёт полный 200, который заменяет старую часть. Нельзя приклеить новую полную версию к прежнему началу.

Экспорт j001 создаётся один раз по ключу, затем проходит queued, running и терминальный succeeded либо failed. 202 относится к принятию, 200 GET — к чтению доступного состояния, а вложенная ошибка работы не делает успешное чтение HTTP 500. Источник фиксируется для задания отдельно от срока курсора.

Событие e001 фиксируется в outbox вместе с обновлением курса. Получатель проверяет подпись исходных байтов, свежесть timestamp и атомарно сохраняет принятие. Потерянный ответ допускает повтор, revision помогает не откатить новое состояние старым событием. Эти решения не обещают атомарную доставку через все независимые внешние системы.

Документы должны говорить одно и то же

Ограниченная OpenAPI из урока 30 описывает GET/PUT, но не весь каталог. Базовый контракт и продолжение содержат более широкие правила. При будущей реализации нужно устранить расхождения между описанием, схемами и наблюдаемым обменом; наличие красивой страницы спецификации этого не подтверждает.

При расширении проверяют не только типы JSON, но и значения по умолчанию, права, срок курсора, совместимость enum и область ключа после смены пути. Стабильный старый URL полезен, если прежний смысл действительно сохраняется. Версия API, ETag представления и snapshot списка остаются отдельными величинами.

Сейчас выполнен авторский разбор текстов и источников, а не runtime-проверка. Среди будущих неподтверждённых областей остаются конкурентные транзакции, хранение сессий, браузерные политики, фактический размер потока, recovery заданий и криптографическая библиотека. Это конкретные границы реализации, которые следуют из выбранного договора.

После этого урока серия даёт основу для разработки сервера на подходящем стеке и отдельного клиента. Для начала полезно выбрать один сквозной сценарий — публичное чтение и условное изменение — и реализовать его строго по документам, затем постепенно добавить остальные операции. Локальные Markdown-уроки уже можно читать последовательно; публикация и выполнение учебных примеров сейчас не проводились.

Примеры продолжения серии. Оглавление курса.