'subscr' capability для WebSocket User API

Обзор

С помощью модуля производится подписка на события системы. Происходящие в системе события подразделяются на классы, упаковываются в объекты с данными о событии и отправляются подписчикам с применением фильтров.

События отправляются только в рамках существующей подписки. Если одно и то же событие подходит под несколько подписок, то по каждой из них отправляется отдельное уведомление. Каждый пир может осуществить произвольное разумное число подписок.

Запросы и ответы

Для подписки необходимо отправить запрос subscribe, указав класс событий и сопутствующую идентификационную информацию.

Подписка имеет время жизни. Для организации длинных циклов требуется регулярное продление подписки. Рекомендованный режим - подписка с указанием уникального идентификатора на период до 3600 секунд, и затем заблаговременно до истечения времени предыдущей подписки отправка запроса с тем же идентификатором и тем же периодом. Остальные поля при этом могут не упоминаться. Для подписок периодом в 300 секунд отправлять повторную подписку оптимально через 240 секунд. Для подписок периодом 3600 секунд — через 3000 секунд.

Подписка может содержать фильтры. В зависимости от того, какой это класс событий, фильтры могут устанавливаться и применяться по разному.

Если запрос отправляется с идентификатором существующей подписки, то он рассматривается как продление (с ненулевым значением 'expires'), либо как отписка (с нулевым значением 'expires'). Удаление подписки производится путем отправки запроса "subscribe" с идентификатором интересующей подписки в поле 'id' и значением 'expires': 0. При удалении не осущетсвляется проверка прав доступа.

Если запрос на продление подписки содержит отличающиеся от оригинального запроса значения других полей ('events', 'objects', 'classpath', 'exargs'), то подписка изменяется в соотвествии с новыми данными. Такой способ изменения не является рекомендованным, поскольку может провоцировать коллизии в транзакциях при обработке плотных потоков событий.

Общий вид запроса:
[
  "subscribe",
  {
    "qid": "16066361-dfc0-49f6-b734-9b3dda09db22",
    "objects": ...,
    "events": ...,
    "expires": ...,
    "id": ...
    "exargs": ...
    ...
  }
]
  • events - список событий для фильтрации типов событий. Указывается список строк, каждая из которых представляет отдельный тип события, например "callevents.dlg_start". Для ряда классов допустимо указывать "callevents.*".

  • objects - список объектов для фильтрации событий. Может быть указано значение "any" или список значений, например '["sipuser1","sipuser2"]'.

  • expires - время жизни подписки в секундах. При необходимости подписчик должен продлить подписку повторной отправкой запроса с ненулевым значением 'expires'.

  • id - идентификатор подписки. Если не задан в запросе, то генерируется автоматически и в любом случае возвращается в ответе для того, чтобы можно было осуществлять управление подпиской: продление и удаление.

  • exargs - объект со специфическими настройками подписки. В частности, при подписке на "modelevents.data_changed" может быть установлен фильтр (filter) и маскировка (mask).

  • classpath - для событий класса 'modelevents' может быть указан вместо 'objects' и содержать полный путь (эндпойнт) класса, например "/rest/v1/model/my/Records" или "/rest/v1/uc/calls".

Допустима подписка на несколько событий одного класса одновременно и на несколько объектов одновременно.

Общий вид успешного ответа:
[
  "subscribe_result",
  {
    "qid": "16066361-dfc0-49f6-b734-9b3dda09db22",
    "success": true,
    "id": ...,
    "msg": "subscribed",
    "domain": ...
  }
]
  • success - флаг-признак успешности подписки.

  • id - идентификатор подписки, с помощью которого можно осуществлять управление подпиской: продление и удаление. Также будет указан в уведомлениях соответствующих, отправленных по этой подписке.

  • msg - результат подписки.

  • domain - домен, в котором осуществлена подписка.

Уведомления

Уведомления отправляются подписчикам с учетом установленных объектов, фильтров, масок. Содержит поле 'data' с содержимым, соответствующим спецификации типа события.

Формат уведомления:
[
  "notify",
  {
    "class": string,
    "eventts": integer,
    "sid": string,
    "type": string,
    "data": object
  }
]
  • class - класс события.

  • type - тип события.

  • eventts - таймштамп события.

  • sid - идентификатор подписки (обязателен для уведомлений класса 'modelevents', в других может отсутствовать).

  • data - содержимое события в соответствии со спецификацией в разделе События.

Пример уведомления:
[
  "notify",
  {
    "class": "modelevents.data_changed",
    "type": "data_changed",
    "eventts": 1692042096897,
    "sid": "81edebaa-0189-f5cd-80e2-7cd30a921f58",
    "data": {
      "classname": "sipusers",
      "classpath":"/rest/v1/uc/sipusers",
      "eid": "57278625-5fa4-eb0c-fb53-f6fa26881f25",
      "operation": "update",
      "entity": {
        "id":"57278625-5fa4-eb0c-fb53-f6fa26881f25","iduser":"e7adf0aa-05b7-8163-948c-3392a9660db9","lic":{"devices":123456789},
        "login": "sip1",
        "name": "SIP1Сип-{D}",
        "phonenumber": "11",
        "reg": 1,
        "opts":{
         "calltimesec": 37,
         "dlgtimesec": 300,
         "title": ""
        }
      },
      "modifier_type": "user",
      "modifier_id": "e7adf0aa-05b7-8163-948c-3392a9660db9"

    }
  }
]

Классы событий

Выделяются следующие категории событий:

  • События об изменениях в модели данных - как статической, так и динамической (modelevents).

  • События телефонии (callevents).

  • События сценариев IVR (ivrevents).

Возможны уведомления также по проектным событиям, генерируемым в сценариях.

События класса modelevents

События этого типа генерируются сразу же при проведении изменений в модели данных - как статической (dc), так и динамической (dms). В классе существует единственное событие "data_changed". Подробное описание событий класса здесь.

При обработке запроса "subscribe" производится авторизация на операцию чтения элементов коллекции. Соответственно, пользователю должна быть добавлена роль, имеющая разрешение на маршрут к endpoint элемента коллекции методом 'GET'.

Событием "notify" доставляются данные о проведенной операции, а также текущее значение измененной сущности с поправкой на маску ('exargs.mask'). При создании подписки с фильтром ('exargs.filter'), события могут быть отфильтрованы. Фильтр и маска задаются в виде, аналогичном параметрам REST запроса GET коллекции элементов (Подробнее).

Доставляемые события по конкретной сущности всегда приводятся к "create", "update" и "delete" на основании сравнения предыдущего значения и нового значения с фильтром. Так, если предыдущее значение не попадало под фильтр, а после изменения попадает, то событие будет доставлено и иметь операцию "create" несмотря на то, что реально была проведена операция "update" или "replace".

Виды операций:

  • create - сущность создана и попадает под фильтр, либо изменена так, что теперь попадает под фильтр подписки.

  • update - сущность изменена, и оба значения - прошлое и текущее - попадают под фильтр подписки.

  • delete - сущность удалена, либо изменена так, что теперь не попадает под фильтр подписки.

  • reload - очередь обработки запросов по классу перезагружена. Для обеспечения целостности требуется повторный REST-запрос на выборку элементов из коллекции с фильтром.

  • clear - коллекция очищена.

  • corrupt - при обработке запроса по сущности возникла проблема с хранилищем, текущее состояние сущности может быть рассогласовано, и требуется обновление сущности REST-запросом.

Корректный запрос на подписку должен содержать ровно один объект (название класса или путь к классу в REST-API) и ровно один тип событий — "modelevents.data_changed".

Для указания конкретной коллекции преимущественно применяется поле 'classpath', в этом случае поле 'objects' игнорируется.

При большых потоках событий использование масок и фильтров способно значимо сократить нагрузку на систему.

Большое количество одновременных подписок с различными фильтрами на одну и ту же коллекцию способно значимо увеличить нагрузку на систему.

Могут использоваться следующие подходы по работе с событиями класса: * подписка на коллекцию с маской, оставляющей только одно-два поля, и запрос на чтение объекта при поступлении события. * подписка без маски и использование объекта непосредственно из тела события. * подписка на короткое время, пока ведетя работа с данными. Например подписка на изменения объекта-запроса с фильтром по идентификатору и состоянию, создание объекта-запроса с этим идентификатором, отслеживание события о переходе объекта-запроса в интересующее состояние, удаление подписки. * кэширование объектов: подписка на коллекцию и следующая за ней вычитка данных в кэш; затем модификация данных в кеше при поступлении событий; периодическая полная вычитка данных в кэш с большим интервалом.

Пример запроса:
[
  "subscribe",
  {
    "qid": 0.15994723,
    "id": "abcdabcd-abcd-abcd-abcd-abcdabcdabcd",
    "events": ["modelevents.data_changed"],
    "classpath": "/rest/v1/model/my/Records",
    "expires": 300,
    "exargs": {
      "filter": ["==",["property","code"],"aaa"],
      "mask": ["id","name"]
    }
  }
]

События класса callevents

События телефонии. Подробное описание событий класса здесь.

В поле 'events' могут быть перечислены интересующие события класса callevents, либо указана полная маска: "callevents.*".

Фильтр по абонентам-участникам диалога, упоминаемым в событии, может быть задан в поле 'objects'.

Пример запроса:
[
  "subscribe",
  {
    "qid": 0.105938272,
    "id": "bcdebcde-bcde-bcde-bcde-bcdebcdebcde",
    "events": ["callevents.*"],
    "objects": ["11"],
    "expires": 300
  }
]

События класса ivrevents

События сценариев IVR. Подробное описание событий класса здесь.

В поле 'events' могут быть перечислены интересующие события класса callevents, либо указана полная маска: "ivrevents.*".

Практическую пользу можно извлечь из событий ivrevents.api_start и ivrevents.api_stop, с помощью которых производится уведомление о готовности к передачи управления сценарием внешнему модулю.

Пример запроса:
[
  "subscribe",
  {
    "qid": 0.105938272,
    "id": "cdefcdef-cdef-cdef-cdef-cdefcdefcdef",
    "events": ["ivrevents.api_start", "ivrevents.api_stop"],
    "objects": ["any"],
    "expires": 300
  }
]