Модуль для Apostol + db-platform — Apostol CRM1.
WebSocket API предоставляет возможность подключения к API системы по протоколу WebSocket. Реализует лёгкий RPC-протокол поверх WebSocket в формате JSON, систему подписки на события (паттерн «Наблюдатель») и REST-эндпоинт для отправки данных подключённым клиентам.
WebSocketAPI тесно связан с модулями observer, notification, notice, message и log платформы db-platform.
C++ модуль отвечает за WebSocket-транспорт и маршрутизацию сессий. Всё состояние подписок, реестр издателей и данные событий находятся исключительно в базе данных:
| Модуль db-platform | Назначение |
|---|---|
observer |
Реестр издателей и подписчиков: хранит издателей (observer.publisher) и активные подписки-слушатели (observer.listener) по сессиям |
notification |
Источник событий издателя notify — срабатывает при каждом взаимодействии пользователя с объектом системы (создание/обновление/удаление/переход) |
notice |
Источник событий издателя notice — системные извещения, сгруппированные по категориям |
message |
Источник событий издателя message — входящие и исходящие сообщения из сущности message |
log |
Источник событий издателя log — записи журнала событий (M/W/E/D) |
Ключевые объекты базы данных:
| Объект | Назначение |
|---|---|
observer.publisher |
Зарегистрированные издатели (notify, notice, message, log, geo) |
observer.listener |
Активные подписки: сессия → издатель с фильтром и параметрами |
api.observer_subscribe(publisher, filter, params) |
Создаёт или обновляет слушателя для текущей сессии |
api.observer_unsubscribe(publisher) |
Удаляет слушателя для текущей сессии |
api.observer_publisher(code) |
Возвращает метаданные издателя |
api.observer_listener(publisher, session) |
Возвращает состояние слушателя для сессии |
[module/WebSocketAPI]
enable=trueСледуйте указаниям по сборке и установке Апостол.
Чтобы установить с сервером соединение WebSocket, клиентское приложение должно выполнить «Рукопожатие» («Opening Handshake»), как описано в RFC 6455, раздел 4.
Сервер накладывает дополнительные ограничения на URL-адрес WebSocket, подробно описанные ниже.
Взаимодействие с системой происходит в рамках ранее созданной сессии. Сессия создаётся после успешной аутентификации пользователя в системе, результатом которой является получение маркера доступа, идентификатора сессии и секретного ключа.
URL подключения содержит код сессии и идентификатор сеанса связи.
Формат URL подключения:
ws[s]://[ws.]example.com/session/<code>[/<identity>]
Где:
<code>— Обязательный. Код сессии (40 символов).<identity>— Необязательный. Идентификатор сеанса связи в рамках сессии. Используется для установки нескольких соединений к одной сессии.
Примеры:
wss://ws.example.com/session/c83b2f85321f95341707624546ca6ac4fa6d1115
wss://ws.example.com/session/c83b2f85321f95341707624546ca6ac4fa6d1115/user1
Протокол WebSocket сам по себе не даёт возможности отправлять сообщения в режиме запрос/ответ. Чтобы обеспечить эту возможность, был создан небольшой RPC-протокол поверх WebSocket в формате JSON.
| Ключ | Расшифровка | Тип данных | Назначение, примечания |
|---|---|---|---|
t |
MessageTypeId | INTEGER | Тип сообщения (описание ниже). |
u |
UniqueId | UUID | Уникальный идентификатор сообщения. Если сообщение от сервера является ответом на запрос от клиента, UniqueId будет одинаковым. |
a |
Action | STRING | Действие (маршрут к конечной точке API). |
c |
ErrorCode | INTEGER | Код ошибки. |
m |
ErrorMessage | STRING | Сообщение об ошибке. |
p |
Payload | JSON | Полезная нагрузка. |
| Тип сообщения | Номер | Направление | Описание |
|---|---|---|---|
OPEN |
0 | Клиент → Сервер | Авторизация. Открытие ранее созданной сессии. |
CLOSE |
1 | Клиент → Сервер | Закрытие сессии (выход из системы). |
CALL |
2 | Клиент ↔ Сервер | Запрос или серверная инициатива. |
CALLRESULT |
3 | Сервер → Клиент | Ответ на запрос. |
CALLERROR |
4 | Сервер → Клиент | Ответ на запрос с ошибкой. |
После подключения клиенту необходимо авторизоваться перед отправкой API-запросов.
Авторизация может быть выполнена в автоматическом режиме при условии, если в момент установки связи были указаны соответствующие HTTP-заголовки:
Authorization: Bearer <token>
Или:
Session: <session>
Secret: <secret>
Если заполнение HTTP-заголовков блокируется используемым клиентским фреймворком, авторизация выполняется путём отправки пакета OPEN с данными авторизации, выданными сервером авторизации: маркером доступа (token) или секретным кодом сессии (secret).
После успешной авторизации можно отправлять API-запросы с типом сообщения CALL. Маршрут к конечной точке API указывается в ключе Action (a), JSON-тело запроса — в ключе Payload (p).
Попытка отправить запрос до выполнения успешной авторизации приведёт к ответу с типом CALLERROR.
Пример — код сессии не найден или сессия закрыта:
{"t":4,"u":"<uuid>","c":400,"m":"Код сессии не найден."}Запрос:
{"t":0,"u":"<uuid>","p":{"secret": "MWCJ14k/RJyiHskQB8DoVbliiwDeNGKsgsAMugp3OZt+M0Zj44hDykwRuFoWEwuG"}}Положительный ответ:
{"t":3,"u":"<uuid>","p":{"authorized": true, "code": "amAJmzkxvDE+ad7KwkRtZU1qkUod+3XuycBbxRqHOOjBdeOkkR+lSExI4L8LAcb+", "message": "Успешно."}}Где code — новый код авторизации для получения маркера доступа (не путать с секретным кодом сессии).
Отрицательный ответ:
{"t":4,"u":"<uuid>","c":401,"m":"Выход из системы. Секретный код сессии не прошёл проверку."}ВНИМАНИЕ: При передаче неверных данных авторизации сессия будет закрыта, но не соединение.
Запрос:
{"t":0,"u":"<uuid>","p":{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiIDogImFjY291bnRzLnBsdWdtZS5ydSIsICJhdWQiIDogInNlcnZpY2UtcGx1Z21lLnJ1IiwgInN1YiIgOiAiYzgzYjJmODUzMjFmOTUzNDE3MDc2MjQ1NDZjYTZhYzRmYTZkMTExNSIsICJpYXQiIDogMTYwNjIxMDcwNiwgImV4cCIgOiAxNjA2MjE0MzA2fQ.ZI82FKXAgA1CZm3gx9XCpgpq_WyZJvwqYI4nOdccVts"}}ВНИМАНИЕ: Маркер доступа имеет ограниченный срок жизни.
Положительный ответ:
{"t":3,"u":"<uuid>","p":{"authorized": true, "message": "Успешно."}}Отрицательный ответ:
{"t":4,"u":"<uuid>","c":403,"m":"Verification failed: Token expired."}Предусмотрена возможность отправки произвольных данных клиентскому приложению, подключённому по WebSocket.
Для этого нужно отправить на сервер REST API запрос:
POST /ws/<code>[/<identity>]
<anydata>
Где:
<code>— Обязательный. Код сессии WebSocket-соединения, на которое необходимо передать данные.<identity>— Необязательный. Идентификатор сеанса связи в рамках сессии (при наличии).<anydata>— Необязательный. Любые данные в произвольном формате.
Данные будут отправлены запросом с типом сообщения CALL: в Action будет указано значение /ws, в Payload — произвольные данные из REST API запроса.
Пример запроса:
POST /ws/8c98085f34c83a0eea5f40791218fbf80f1858d3 HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.[...].9GI82ffkIhbUeWR8if3a8q78nfXAL4AFOMp3kWDTHOA
Content-Type: application/json
{"anydata":null}Положительный ответ:
{"sent": true, "status": "Success"}Отрицательный ответ:
{"sent": false, "status": "Session not found"}Для того чтобы получать данные от сервера без предварительных запросов со стороны клиентского приложения, нужно подписаться на события.
Для подписки необходимо выбрать издателя и настроить слушателя (установить фильтр и параметры).
Издатель notify предоставляет возможность подписаться на системные события, которые возникают каждый раз, когда пользователь системы взаимодействует с тем или иным объектом.
{
"entities": ["<код>", ...],
"classes": ["<код>", ...],
"actions": ["<код>", ...],
"methods": ["<код>", ...],
"objects": [<id>, ...]
}| Поле | Тип | Описание |
|---|---|---|
entities |
JSON array | Необязательный. Сущность. Массив кодов. |
classes |
JSON array | Необязательный. Класс. Массив кодов. |
actions |
JSON array | Необязательный. Действие. Массив кодов. |
methods |
JSON array | Необязательный. Метод. Массив кодов. |
objects |
JSON array | Необязательный. Объекты. Массив идентификаторов. |
ВАЖНО: Фильтр по полям работает по условию И, по значениям в поле — по условию ИЛИ. Поля, в которых не заданы значения, игнорируются.
{
"type": "notify | object | mixed | hook",
"hook": { ... }
}| Поле | Тип | Значение | Описание |
|---|---|---|---|
type |
STRING | notify, object, mixed, hook |
Необязательный. Тип ответа. |
hook |
JSON | Hook | Обязательный для типа hook. Ловушка. |
notify— в ответ будут приходить сами уведомления.object— в ответ будут приходить данные объекта в формате/getзапроса.mixed— в ответ будут приходить и уведомления, и данные объекта.hook— ответом будет результат выполнения API-запроса изhook.
Ловушка задаёт параметры выполнения запроса API. При каждом срабатывании условий подписки ответом будут данные из запроса ловушки.
{
"method": "POST | GET",
"path": "<путь>",
"payload": { ... }
}| Поле | Тип | Описание |
|---|---|---|
method |
STRING | Необязательный. HTTP-метод (POST или GET). |
path |
STRING | Обязательный. REST API путь к конечной точке. |
payload |
JSON | Вариативный. Полезная нагрузка. Зависит от запроса. |
Издатель notice предоставляет возможность подписаться на системные извещения.
{
"categories": ["<код>", ...]
}| Поле | Тип | Описание |
|---|---|---|
categories |
JSON array | Необязательный. Категория. Массив кодов. |
{
"type": "notify"
}| Поле | Тип | Описание |
|---|---|---|
type |
STRING | Необязательный. Тип ответа. В настоящий момент — notify. |
Издатель message предоставляет возможность подписаться на входящие и исходящие сообщения.
{
"classes": ["inbox", "outbox"],
"types": ["<код>", ...],
"agents": ["<код>", ...],
"codes": ["<код>", ...],
"profiles": ["<значение>", ...],
"addresses": ["<значение>", ...],
"subjects": ["<значение>", ...]
}| Поле | Тип | Описание |
|---|---|---|
classes |
JSON array | Необязательный. Класс сообщения: inbox (входящее) или outbox (исходящее). |
types |
JSON array | Необязательный. Код типа агента. |
agents |
JSON array | Необязательный. Код агента. |
codes |
JSON array | Необязательный. Код сообщения. |
profiles |
JSON array | Необязательный. Профиль настроек или адрес отправителя. |
addresses |
JSON array | Необязательный. Адрес получателя. Для API запросов — это маршрут REST API. |
subjects |
JSON array | Необязательный. Тема сообщения. |
{
"type": "notify"
}| Поле | Тип | Описание |
|---|---|---|
type |
STRING | Необязательный. Тип ответа. В настоящий момент — notify. |
Издатель log предоставляет возможность подписаться на журнал событий.
{
"types": ["M", "W", "E", "D"],
"codes": [<число>, ...],
"categories": ["<код>", ...]
}| Поле | Тип | Описание |
|---|---|---|
types |
JSON array | Необязательный. Тип записи: M (Message), W (Warning), E (Error), D (Debug). |
codes |
JSON array | Необязательный. Числовой код записи. |
categories |
JSON array | Необязательный. Категория. |
{
"type": "notify"
}| Поле | Тип | Описание |
|---|---|---|
type |
STRING | Необязательный. Тип ответа. В настоящий момент — notify. |
Издатель geo предоставляет возможность подписаться на поступающие данные геолокации.
{
"codes": ["<код>", ...],
"objects": [<id>, ...]
}| Поле | Тип | Описание |
|---|---|---|
codes |
JSON array | Необязательный. Коды групп координат (мест положений). По умолчанию default. |
objects |
JSON array | Необязательный. Объекты. Массив идентификаторов. |
{
"type": "notify"
}| Поле | Тип | Описание |
|---|---|---|
type |
STRING | Необязательный. Тип ответа. В настоящий момент — notify. |
POST /api/v1/observer/subscribe
Подписаться на события издателя.
Параметры запроса:
| Поле | Тип | Значение | Описание |
|---|---|---|---|
publisher |
STRING | notify, notice, message, log, geo |
Обязательный. Код издателя. |
filter |
JSON | Необязательный. Фильтр отбора событий издателя. | |
params |
JSON | Необязательный. Параметры слушателя. |
Примеры:
Подписаться на все события издателя notify:
{"t":2,"u":"<uuid>","a":"/observer/subscribe","p":{"publisher":"notify"}}Подписаться на события издателя notify с фильтром (классы: client, device) и типом ответа object:
{"t":2,"u":"<uuid>","a":"/observer/subscribe","p":{"publisher":"notify","filter":{"classes":["client","device"]},"params":{"type":"object"}}}Подписаться на все входящие сообщения:
{"t":2,"u":"observer","a":"/observer/subscribe","p":{"publisher":"notify","filter":{"entities":["message"],"classes":["inbox"],"actions":["create"]},"params":{"type":"object"}}}Отловить создание нового клиента и получить данные в виде списка клиентов:
{"t":2,"u":"<uuid>","a":"/observer/subscribe","p":{"publisher":"notify","filter":{"classes":["client"],"actions":["create"]},"params":{"type":"hook","hook":{"path":"/api/v1/client/list","payload":{}}}}}POST /api/v1/observer/unsubscribe
Отписаться от событий издателя.
Параметры запроса:
| Поле | Тип | Значение | Описание |
|---|---|---|---|
publisher |
STRING | notify, notice, message, log, geo |
Обязательный. Код издателя. |
Пример:
{"t":2,"u":"<uuid>","a":"/observer/unsubscribe","p":{"publisher":"notify"}}POST /api/v1/observer/publisher
Параметры запроса:
| Поле | Тип | Значение | Описание |
|---|---|---|---|
code |
STRING | notify, notice, message, log, geo |
Обязательный. Код издателя. |
fields |
JSON array | Необязательный. Массив имён полей для возврата. Если не указано — возвращаются все поля. |
POST /api/v1/observer/publisher/get
Параметры запроса:
| Поле | Тип | Значение | Описание |
|---|---|---|---|
code |
STRING | notify, notice, message, log, geo |
Обязательный. Код издателя. |
fields |
JSON array | Необязательный. Массив имён полей для возврата. Если не указано — возвращаются все поля. |
POST /api/v1/observer/publisher/count
Параметры запроса: Общие параметры запроса для списка
POST /api/v1/observer/publisher/list
Параметры запроса: Общие параметры запроса для списка
POST /api/v1/observer/listener
Параметры запроса:
| Поле | Тип | Значение | Описание |
|---|---|---|---|
publisher |
STRING | notify, notice, message, log, geo |
Обязательный. Код издателя. |
session |
STRING | Необязательный. Код сессии. | |
fields |
JSON array | Необязательный. Массив имён полей для возврата. Если не указано — возвращаются все поля. |
POST /api/v1/observer/listener/set
Параметры запроса:
| Поле | Тип | Описание |
|---|---|---|
publisher |
STRING | Обязательный. Идентификатор издателя. |
session |
STRING | Необязательный. Код сессии. |
filter |
JSON | Необязательный. Фильтр отбора событий издателя. |
params |
JSON | Необязательный. Параметры слушателя. |
POST /api/v1/observer/listener/get
Параметры запроса:
| Поле | Тип | Описание |
|---|---|---|
publisher |
STRING | Обязательный. Код издателя. |
session |
STRING | Необязательный. Код сессии. |
fields |
JSON array | Необязательный. Массив имён полей для возврата. Если не указано — возвращаются все поля. |
POST /api/v1/observer/listener/count
Параметры запроса: Общие параметры запроса для списка
POST /api/v1/observer/listener/list
Параметры запроса: Общие параметры запроса для списка
Запрос "Кто я":
{"t":2,"u":"<uuid>","a":"/whoami"}Запрос сущностей:
{"t":2,"u":"<uuid>","a":"/entity","p":{"fields":["id","code","name"]}}Запрос классов:
{"t":2,"u":"<uuid>","a":"/class","p":{"fields":["id","entity","entitycode","entityname","code","label"]}}Запрос действий:
{"t":2,"u":"<uuid>","a":"/action","p":{"fields":["id","code","name"]}}Запрос методов:
{"t":2,"u":"<uuid>","a":"/method","p":{"fields":["id","class","classcode","classlabel","action","actioncode","actionname","code","label"]}}Footnotes
-
Apostol CRM — абстрактный термин, а не самостоятельный продукт. Он обозначает любой проект, в котором совместно используются фреймворк Apostol (C++) и db-platform через специально разработанные модули и процессы. Каждый фреймворк можно использовать независимо; вместе они образуют полноценную backend-платформу. ↩