Перейти к основному содержимому
Версия: 4.x

Протокол

На этой странице описан формат передачи данных — для отладки и для написания клиента на другом языке. Официальные клиенты реализуют его полностью.

Транспорт​

  • WebSocket upgrade на путь, переданный в MapDarkWs. Браузеры отправляют заголовок Origin; DarkWS его не проверяет, но это умеет WebSocketOptions.AllowedOrigins из ASP.NET Core (см. Безопасность).
  • Необязательный query-параметр (по умолчанию token, AuthenticationQueryParameter) передаётся в аутентификатор сервера. Если аутентификатор не возвращает сессию, соединение принимается анонимно. Если он выбрасывает исключение, оно выходит за пределы эндпоинта, и upgrade завершается ошибкой сервера.
  • Только текстовые фреймы. Сейчас сервер обрабатывает бинарный фрейм как текст с теми же байтами, а .NET-клиент закрывает сокет с кодом 1003; не полагайтесь на бинарные фреймы.
  • Сообщения больше серверного MaxMessageSizeBytes (1 MiB) закрывают соединение с кодом 1009.

JSON-сообщения​

НаправлениеФормат
Запрос{ "id": string, "action": string, "data"?: unknown }
Успех{ "id": string, "data"?: unknown }
Ошибка{ "id": string, "error": string, "data"?: unknown }
Рассылка{ "id": "@", "action": string, "data"?: unknown }
→ { "id": "7f3c", "action": "math:sum", "data": { "left": 2, "right": 3 } }
← { "id": "7f3c", "data": { "value": 5 } }

→ { "id": "7f3d", "action": "message:delete", "data": { "id": "42" } }
← { "id": "7f3d", "error": "message:forbidden" }

← { "id": "@", "action": "message:created", "data": { "text": "hi" } }
  • Идентификаторы запросов выбирает клиент. Они должны быть непустыми и не могут быть @ или @auth; запрос с зарезервированным id отклоняется, а его действие не выполняется.
  • Ответы несут id запроса и могут приходить в любом порядке.
  • Ответ считается ошибкой всякий раз, когда в нём есть поле error.
  • В ответах data опускается только тогда, когда сервер использовал перегрузку без данных (Ok(), Error(code)). Ok(value) и Error(code, details) всегда записывают его, а для значения null — как null.
  • В рассылках data опускается, если сервер разослал сообщение без данных или со значением null.
  • Имена полей конверта зафиксированы атрибутами JsonPropertyName: id, action, data и error. Политики именования JsonOptions применяются к свойствам прикладных DTO внутри data (по умолчанию camelCase), а не к именам конверта.
  • Ответ invalid-request на запрос с зарезервированным id использует пустой id, чтобы его нельзя было спутать с рассылкой или управляющим ответом. Сообщение, которое не является JSON или не содержит читаемого строкового id, остаётся вовсе без ответа.

Текстовые команды​

Системные команды и ответы на них передаются обычным текстом, без JSON и id:

КомандаУспехОшибка
auth:<token>auth:successauth:failed
logoutlogout:successСоединение обрывается, если logout не удаётся завершить
pingpongНет

auth: с пустым токеном, с отклонённым токеном или с аутентификатором, выбросившим исключение, получает ответ auth:failed и очищает прежнюю сессию соединения. logout тоже очищает сессию. Уже выполняющиеся запросы не откатываются.

Поскольку у ответов нет id, клиент должен отправлять не более одной команды auth:/logout за раз и дожидаться ответа на неё. Если ответ не пришёл вовремя, клиенту следует закрыть сокет, чтобы запоздавший ответ не был принят за ответ на следующую команду. Сервер читает сообщения по одному и завершает обработку команды, прежде чем прочитать следующее сообщение.

Регулярно отправляйте ping: сервер на .NET 8 закрывает соединения, молчащие дольше ReceiveIdleTimeout (2 минуты). .NET-клиент считает отсутствие pong признаком мёртвого соединения; браузерный клиент отправляет ping, но ответ не проверяет. Пока соединение находится на пределе числа запросов, сервер перестаёт читать, поэтому медленные обработчики могут задерживать pong.

Коды ошибок​

КодЗначение
darkws:error:invalid-actionТакого действия нет
darkws:error:invalid-requestОтсутствующие или неверные поля конверта, зарезервированный id либо payload, который не удаётся привязать
darkws:error:authorization-requiredДействию нужна аутентифицированная сессия
darkws:error:request-failedОбработчик неожиданно завершился с ошибкой
auth:failedТекстовый ответ на отклонённую команду auth:

Серверы могут переименовать коды darkws:error:* через DarkWsOptions. Все остальные коды приходят из приложения.

Коды закрытия и HTTP-статусы​

КодКто отправляетПричина
HTTP 400СерверЭто не WebSocket-запрос
HTTP 403ASP.NET CoreOrigin отсутствует в WebSocketOptions.AllowedOrigins
Ошибка сервера (обычно HTTP 500)ASP.NET CoreАутентификатор при upgrade выбросил исключение
1000Любая сторонаНормальное закрытие
1003.NET-клиентСервер отправил бинарный фрейм
1009Сервер или .NET-клиентСообщение превысило лимит размера

Контракт жизненного цикла клиентов​

Оба клиента говорят на одном протоколе, но придерживаются разных политик жизненного цикла:

СлучайБраузерный DarkWs.NET DarkWsClient
Сокет обрывается при отключённом переподключенииОжидающие вызовы завершаются ошибкой; фоновых повторов нет. Следующий запрос или отправка открывает новый сокет.Ожидающие вызовы завершаются ошибкой. Последующие вызовы завершаются ошибкой до явного ConnectAsync.
Сессия на новом сокетеАвтоматической аутентификации нет. query для upgrade строится заново для каждого сокета; иначе вызывайте authenticate() после каждого open.AuthenticationTokenProvider, если он задан, аутентифицирует каждый сокет до отправки запросов.
Автоматическая аутентификация не удалась (auth:failed, провайдер выбросил исключение или не вернул токен)Неприменимо.Готовность завершается постоянной ошибкой: состояние становится Disconnected с указанием причины, и повторы прекращаются до ConnectAsync.

В браузере connected и open отражают состояние транспорта и не доказывают, что аутентификация прошла успешно. В .NET Connected наступает после успешной автоматической аутентификации.

Ни один из клиентов не хранит токены, переданные для ручной аутентификации. Переподключения используют только query приложения (браузер) или провайдер токена (.NET).