Репозиторий содержит описание протокола MQTT-RPC.
Эталонная реализация на Python доступна тут: python-mqtt-rpc.
MQTT-RPC предполагает удалённый вызов клиентом процедур сервиса. Коммуникация осуществляется через MQTT. Формируются топик для запросов и топик для ответов. В качестве значений передаются JSON объекты.
Для клиентов сервисов топик для отправки запросов выглядит как:
/rpc/v1/<driver>/<service>/<method>/<client_id>
Где:
/rpc/v1
- общий префикс для всех сервисов RPC версии 1,driver
- имя драйвера, предоставляющего сервисы,service
- имя сервиса. Соответствует, например, имени класса с методами в коде сервера,method
- имя метода, предоставленного сервисом,client_id
- идентификатор клиента (можно использовать mqtt client id, если он уникальный, или просто сгенерить что-то рандомное, например, UUID v4).
Например:
/rpc/v1/wb_logs/logs/List/3b131342-9809-4bf0-a036-cbcebd5f42e5
Сервер отправляет ответ на топик, по которому пришёл запрос, с добавлением в конце /reply. Для того чтобы получить ответ от сервиса на свой RPC-зпарос, клиент подписывается на:
/rpc/v1/<driver>/<service>/<method>/<client_id>/reply
client_id
здесь - собственный ID клиента, совпадающий с client_id
в RPC-запросе.
Например:
/rpc/v1/wb_logs/logs/List/3b131342-9809-4bf0-a036-cbcebd5f42e5/reply
Для того, чтобы рассмотреть все доступные RPC-сервисы и их методы в системе можно запустить команду в терминале контроллера:
mosquitto_sub -t "/rpc/v1/+/+/+" -v
Клиент отправляет посылку в виде JSON объекта. Структура объекта представлена в документации к сервису. Например:
{
"id": "1234",
"params": {"A": 1, "B": 2}
}
Здесь:
-
id
- идентификатор транзакции. Для простоты реализации сейчас это должна быть строка, являющаяся десятичным представлением 64-битного беззнакового значения. Спецификация JSON-RPC разрешает использование любой строки, но вышеназванное ограничение немного упрощает Go-реализацию, т.к. стандартный пакет net/rpc использует 64-битные идентификаторы транзакций. Использование числовых значений вместо строк - не очень, т.к. JSON'овый Number - это double, и все значения uint64 он вместить не способен. Зачем вообще нужен идентификатор транзакции: допустим, делается несколько запросов GetValue(name) с разным значением параметра name. Запросы обрабатываются асинхронно и ответы могут придти в разном порядке. Возвращается просто число. Как клиенту отличить возвращаемые значения для разных name? Добавлять входные параметры в ответ было бы менее практичным и несовместимым с неидемпотентными операциями. Если использовать идентификатор транзакции в топике, то это приведёт к необходимости subscribe + unsubscribe на каждом RPC-запросе. Subscribe и unsubscribe в MQTT реализованы по аналогии с QoS1 - с подтверждением: SUBSCRIBE - SUBACK, UNSUBSCRIBE-UNSUBACK, так что избыточного MQTT-трафика выйдет порядочно. -
params
- параметры - JSON-объект или массив значений. Go-реализация использует только представление в виде объекта.
Ответ в случае успеха выглядит как:
{ "id": "1234", "result": 42, "error": null }
id
должен соответствовать идентификатору запроса.result
может быть любым JSON-значением (числом, строкой, объектом, массивом...)
В случае ошибки ответ имеет структуру:
{ "id": "1234", "error": { "message": "divide by zero", "code": -1, "data": "ErrorType"} }
или
{ "id": "1234", "error": { "message": "divide by zero", "code": -1} }
id
должен соответствовать идентификатору запроса.error
должен содержать сообщение об ошибке.
Во всех случаях используется строгий стандартный JSON, т.е. комментарии не поддерживаются и все ключи объектов должны быть в кавычках. Отдельно стоит обратить внимание на то, что значения Inf и NaN в JSON не кодируются.