Протокол
На этой странице описан формат передачи данных — для отладки и для написания клиента на другом языке. Официальные клиенты реализуют его полностью.
Транспорт
- 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:success | auth:failed |
logout | logout:success | Соединение обрывается, если logout не удаётся завершить |
ping | pong | Нет |
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 403 | ASP.NET Core | Origin отсутствует в 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).