Клиент-серверный протокол
Актуально для: SimpleTwo 0.9.x · Версия протокола: 1 · Проверено: 20.09.2026
Это контракт, а не описание текущей реализ ации: то, что здесь написано, продукт обязан соблюдать, а менять это можно только по правилам из §14. Страница адресована тем, кто пишет собственного клиента, бота, мост или интеграцию.
1. Единственное правило, из которого следует остальное
Идентификатор — серверный, имя — читательское, а то, что написал клиент, никогда не является удостоверением личности.
Отправителя аутентифицировала служба — значит, только она может сказать, кто говорит. Клиент сообщает, что он делает и кому, но никогда — кто он.
Три следствия, применяемые везде ниже:
- Личность проставляет сервер.
sender,from,userпишет служба из аутентифицированной сессии. У типа запроса по возможности просто нет такого поля — чтобы клиент физически не мог его заявить. - Адресацию проверяет сервер.
toсверяется с членством, а не принимается на веру. - Поле, которое нужно игнорировать, должно быть удалено. Поле, которое читателю велено пропускать, — это поле, которому поверит следующий читатель.
2. Транспорт и аутентификация
| База | https://<хост messaging>/v1 |
| Аутентификация | Authorization: Bearer <токен> в каждом запросе, включая рукопожатие сокета |
| Боты | тот же заголовок с токеном bot_…; ограничены областями и списком бесед, каждый запрос аудируется, включая чтения |
| Ошибки | JSON {"code": "<идентификатор>", "error": "<фраза>"} |
| События | один WebSocket: GET /v1/ws?token=…&device=… |
Коды состояния несут смысл, и клиент обязан их различать:
| Код | Значит | Что делает клиент |
|---|---|---|
| 400 | запрос неверен или называет неизвестное | это дефект: сообщить, не повторять |
| 401 | токен отсутствует, истёк или отозван | обновить и повторить один раз |
| 403 | аутентифицирован и не имеет права | не повторять: это ответ, а не сбой |
| 404 | нет такого — или нет для этого вызывающего | считать отсутствующим |
| 409 | конфликт состояния (повторное вступление, занятое имя) | разрешить и повторить |
| 410 | этой формы больше нет; в сообщении названа замена | чинить клиент |
| 429 | ограничение частоты | отступить; на сокет это не влияет |
403 намеренно покрывает и «не существует», и «не ваше» везде, где различить их означало бы раскрыть каталог, который вызывающему не виден.
3. Версия протокола, и что говорят старому клиенту
Одно монотонное целое на весь контракт — и HTTP, и кадры сокета вместе. Не semver: единственный вопрос, который кто-либо задаёт, — «этот клиент старше, чем требует сервер?», а это сравнение. Одно число на обе поверхности потому, что это один контракт: день, когда они разъедутся, — это день, когда клиент сможет удовлетворить половину.
| Заголовок | Направление | Значение |
|---|---|---|
X-SimpleTwo-Protocol | сервер → клиент | что говорит служба |
X-SimpleTwo-Protocol-Min | сервер → клиент | самый старый клиент, который ещё обслуживается |
X-SimpleTwo-Client-Protocol | клиент → сервер | что говорит клиент |
Именно заголовки, а не поля тела: клиент обязан иметь возможность узнать, что отстал, из того запроса, который он и так делал, — в том числе из неудачного. Заголовок переживает 4xx, тело которого клиент не понял.
Три состояния, и все три обязаны существовать:
| Клиент относительно сервера | Сервер | Клиент |
|---|---|---|
| текущий | обычная работа | ничего |
отстал, но не ниже Min | обычная работа, считается по версиям | ненавязчивое «доступно обновление» |
ниже Min | отказ: client_too_old | блокирующий экран: эта сборка не говорит с этим сервером |
Без неё любое изменение контракта — выбор между «сломать людей молча» и «нести алиас вечно», а
алиас нельзя снять никогда: доказать, что последний старый клиент исчез, невозможно. Со средней
строкой у отказа от старой формы появляется конец: поднять Min в назначенный день, посмотрев
на отчёт о населении, после того как приложение неделями предупреждало людей само.
Служба сообщает не только отказы, но и состав подключённых: /healthz и консоль разбивают
клиентов по версии протокола и платформе. Одного счётчика мало: «0,4% Android на протоколе 5» и
«31% iOS на протоколе 5» — это одно и то же «больше нуля» и совершенно разные решения.
4. У ошибок есть коды
Ошибка — это {"code", "error"}: код для ветвления, фраза для журнала. Формулировку владеет
клиент, а не сервер: перевод живёт в клиентском ядре. Каталог кодов — на отдельной странице
Коды ошибок, и он проверяется в CI: код, который служба может отправить, а клиент
не умеет перевести, — это дефект сборки, а не мелочь перевода.
Код — это идентификатор: он никогда не переименовывается и не переиспользуется. Переименование молча меняет то, что покажет уже установленный клиент.