что такое json rpc

JSON-RPC

Понравилась статья? Поделись:

JSON-RPC – протокол удаленного вызова процедур, который использует JSON (RFC 4627) в качестве формата данных. Данный протокол во многом похож на XML-RPC, спецификация определяет несколько типов данных и правила их обработки. JSON-RPC поддерживает уведомления и множественные вызовы, так как информация, приходящая на сервер не требует ответа. Он разработан, чтобы быть простым!

Содержание

История [ править ]

Первая версия протокола была представлена в 2005 году и по настоящий момент является официальной. В 2007 году вышло первое дополнение 1.1 WD, где были добавлены специфичные параметры, именованные коды ошибок и функции самонаблюдения. После были выпущены альтернативные соглашения 1.1 Alt и 1.1 Object Specification, также в 2007 году была выпущена версия 1.2, которая в дальнейшем была переименована в 2.0, добавлены именованные параметры и очередь вызовов. Последнее обновление было представлено в 2013 году.

Использование [ править ]

JSON-RPC производит отсылку запросов к серверу, реализующему удаленный протокол. Как клиент в большинстве случаев выступает программа, которой необходимо вызвать метод на удаленной системе. Входные параметры передаются в качестве массива или объекта, в некоторых случаях возможна передача выходных данных. Процесс осуществляется посредством отправки запроса на удаленный сервер через http или tcp/ip сокет. В случае http заголовок Content-Type определяется как application/json.

Особенности [ править ]

JSON-RPC 2.0 [ править ]

Условные обозначения [ править ]

Ключевые обозначения «MUST», «MUST NOT», «REQUIRED», «SHALL», «SHALL NOT», «SHOULD», «SHOULD NOT», «RECOMMENDED», «MAY», and «OPTIONAL» следует интерпретировать как в RFC 2119.

Поскольку JSON-RPC базируется на JSON, ему свойственен тот же тип системы (RFC 4627 ). В JSON могут быть реализованы четыре простых типа (string, Numbers, Booleans, и Null) и два структурированных типа (Objects и Arrays). Термин «Primitive» в этом описании ссылается на любой из этих четырех примитивных типов JSON. Термин «Structured» ссылается на структурированные типы JSON. В каждом случае, относящемуся к любому типу JSON, первая буква всегда будет заглавная: Object, Array, String, Number, Boolean, Null. True и False также всегда указываются с заглавной.

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

Клиент определяется как источник объектов Request и обработчик объектов Response. Сервер определяется как источник объектов Response и обработчик объектов Request. Одна из реализаций этой спецификации может легко выполнить обе эти роли, даже в случае одного или нескольких клиентов. Данная спецификация не учитывает уровень сложности.

Совместимость [ править ]

Объект запроса [ править ]

Вызов RPC представляется путем отправки объекта Request на сервер. Объект Request имеет следующие элементы:

jsonrpc Строка, указывающая версию протокола JSON-RPC должна быть записана в точности как «2.0».

method Строка, содержащая имя метода, который необходимо вызвать. Имена методов, начинающиеся со слова rpc, за которым следует символ периода (U + 002E или ASCII 46), зарезервированы для rpc-внутренних методов и расширений и НЕ ДОЛЖНЫ использоваться ни для чего другого.

Params Структурированная переменная, содержащая значения параметров, которые будут использоваться во время вызова метода. Такой элемент МОЖЕТ быть опущен.

id Идентификатор, установленный Клиентом, который ДОЛЖЕН содержать значение String, Number или Null, если включен. Если он не включен, предполагается, что это уведомление. Значение ДОЛЖНО, как правило, не быть нулевым, а числа НЕ ДОЛЖНЫ содержать дробные части.

Сервер ДОЛЖЕН отвечать с тем же значением в объекте Response, если он включен. Этот элемент используется для корреляции контекста между двумя объектами. Использование Null в качестве значения для элемента id в объекте Request не рекомендуется, поскольку эта спецификация использует значение Null для ответов с неизвестным идентификатором. Помимо этого, поскольку JSON-RPC 1.0 использует значение id для Null для уведомлений, это может вызвать путаницу при обработке.

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

Уведомление [ править ]

Структуры параметров [ править ]

Если они присутствуют, параметры для вызова RPC ДОЛЖНЫ быть представлены как структурированные переменные. По позиции через массив либо по имени через объект. по позиции: params ДОЛЖНЫ быть массивом, содержащим значения в ожидаемом порядке сервера. по имени: params ДОЛЖЕН быть объектом, с именами участников, которые соответствуют именам ожидаемых параметров сервера. Отсутствие ожидаемых имен МОЖЕТ привести к генерированию ошибки. Имена ДОЛЖНЫ точно совпадать, включая случай, с ожидаемыми параметрами метода.

Объект ответа [ править ]

Когда выполняется вызов RPC, сервер ДОЛЖЕН отвечать как Response, за исключением случаев уведомлений. Ответ выражается как один объект JSON со следующими членами:

jsonrpc Строка, указывающая версию протокола JSON-RPC должна быть записана точно как «2.0».

result Этот элемент НЕОБХОДИМ для успеха. Этот элемент НЕ ДОЛЖЕН существовать, если возникла ошибка при вызове метода. Значение этого элемента определяется методом, вызываемым на сервере.

id Этот элемент НЕОБХОДИМ. Он ДОЛЖЕН быть таким же, как значение элемента id в объекте Request. Если при обнаружении идентификатора в объекте Request возникла ошибка (например, ошибка Parse / Invalid Request), она ДОЛЖНА быть Null. Любой элемент результата или элемент ошибки ДОЛЖЕН быть включен, но одновременно оба члена НЕ ДОЛЖНЫ быть включены.

Объект ошибки [ править ]

Когда вызов RPC встречает ошибку, объект ответа ДОЛЖЕН содержать элемент ошибки со значением, являющимся объектом со следующими элементами:

code Номер, указывающий тип ошибки, который произошел. Номер ДОЛЖЕН быть целым числом.

message Строка, содержащая краткое описание ошибки. Сообщение ДОЛЖНО быть ограничено кратким единственным предложением.

data Примитивное или структурированное значение, содержащее дополнительную информацию об ошибке. Этот элемент может быть опущен. Значение этого элемента определяется сервером (например, подробная информация об ошибках, вложенные ошибки и т. Д.).

Остальная часть доступна для определенных пользователем ошибок.

Batch [ править ]

Чтобы отправить несколько объектов Request одновременно, клиент может отправить массив, заполненный объектами Request.

Сервер должен ответить массивом, содержащим соответствующие объекты Response, после того, как все объекты запроса пакета были обработаны. Объект Response ДОЛЖЕН существовать для каждого объекта Request, за исключением тех, что НЕ ДОЛЖНЫ быть объектами Response для уведомлений. Сервер МОЖЕТ обрабатывать пакетный вызов RPC как набор одновременных задач, обрабатывая их в любом порядке и с любой шириной параллелизма.

Объекты Response, возвращаемые из пакетного вызова, могут быть возвращены в массиве в любом порядке. Клиент ДОЛЖЕН соответствовать контексту между набором объектов Request и результирующим набором объектов Response на основе элемента id в каждом объекте.

Если сам вызов пакета rpc не может быть признан допустимым JSON или как массив с хотя бы одним значением, ответ от сервера ДОЛЖЕН быть единственным объектом Response. Если в массиве Response отсутствуют объекты Response, которые должны быть отправлены клиенту, сервер НЕ ДОЛЖЕН возвращать пустой массив, в таком случае он ничего не должен возвращать.

Расширения [ править ]

Имена методов, начинающиеся с rpc. зарезервированы для системных расширений и НЕ ДОЛЖНЫ использоваться ни для чего другого. Каждое расширение системы определено в соответствующей спецификации. Все расширения системы являются ДОПОЛНИТЕЛЬНЫМИ.

Источник

Подробнее о JSON RPC

Не хочу разводить очередной холивар на эту тему. Поэтому, если вкратце, то GraphQL — это сложно, RPC — быстро, REST — некий медиум, но не хватает batch-запросов. И если у вас небольшое приложение или микросервис, то rpc, он же “вызов удаленной процедуры”, может оказаться гораздо лучше и экономичнее для вашей архитектуры, особенно если она основана на микросервисном подходе.

Читайте также:  что такое anti backstab

На моей практике это решение оказалось достаточно удобным и простым, но при этом мощным и расширяемым. И меня сильно удивило, что в мире node.js оно применяется крайне редко.

Суть подхода достаточно примитивна
Запрос включает в себя 4 поля:

Если вы не отправили id, то это означает, что ответ вас не интересует и от сервера вы ничего не получите. Такой вызов называется нотификацией.

Ответ может иметь следующие поля:

Вишенка на торте — это Batch-запрос!

Это означает, что мы можем слепить несколько отдельных ajax запросов в один и отдать его серверу в виде массива запросов. А при получении по заданным id мы поймем, на какой запрос получили ответ.

Небольшая тонкость!

Учтите, что по протоколу сервер не гарантирует последовательность элементов в ответе, например:

У нас имеется три основных объекта:

methods — наши JSON-RPC методы, функции которые реализуют основную логику. Это могут быть ваши контроллеры.

beforeMethods (опциональные) — хук, который будет вызван перед выполнением метода. Здесь вы можете валидировать переданные параметры и выкидывать ошибки, которые попадут в вышестоящий обработчик. Название хука должно совпадать с названием метода, к которому этот хук относится, иначе будет вызвано исключение с сообщением, содержащим информацию о том, какой хук был назван неправильно.

afterMethods (опционально) — хук, который по своей логике похож на предыдущий. Разница только в том, что он будет вызван после выполнения метода и в качестве второго параметра получит результат выполнения. Может быть удобен для логгирования запросов.

Также предоставлен callback (onError) — функция, в которую будет передана ошибка, если она случилась. Тут можно удобно подключить логгер ошибок вроде sentry.io.

Обратите внимание, что bodyParser вызван перед роутером. Можете использовать другой вариант парсинга тела запроса, главное, чтобы в req.body мы получили параметры запроса в формате JSONRPC.

Библиотека в стадии разработки, так что если у вас есть пожелания или идеи, смело пишите в комментариях или открывайте github issues.

Источник

JSON-RPC на C++

В предыдущей статье про декларативный JSON-сериализатор я рассказал, как с помощью шаблонов C++ можно описывать структуры данных и сериализовать их. Это очень удобно, т.к. не только сокращает размер кода, но и минимизирует количество возможных ошибок.Концепция — если код компилируется, то он работает. Примерно такой же подход применен и в wjrpc, о которой речь пойдет в этой статье. Но поскольку wjrpc “выдран” из фреймворка, под интерфейсы которого он был разработан, я также затрону вопросы архитектуры и асинхронных интерфейсов.

Я не буду описывать JSON-сериализатор, на котором работает wjrpc и с помощью которого осуществляется JSON-описание для структур данных сообщений. Про wjson я рассказал в предыдущей статье. Прежде чем рассматривать декларативный вариант описания API сервисов для JSON-RPC, рассмотрим, как можно реализовать разбор в “вручную”. Это потребует написания большего количества run-time кода по извлечению данных и проверок, но он проще для понимания. Все примеры вы можете найти в разделе examples проекта.

В качестве примера рассмотрим API простого калькулятора, который может выполнять операции сложения, вычитания, умножения и деления. Сами запросы тривиальны, поэтому приведу только код только для операции сложения:

Я предпочитаю структуры для каждой пары запрос-ответ размещать в отдельном файле в специально отведенной для этого папке. Это здорово облегчает навигацию по проекту. Использование пространств имен и определение вспомогательных типов, как показано в примере выше, не обязательно, но повышает читабельность кода. Смысл этих typedef-ов поясню позднее.

Для каждого запроса создаем описание JSON.

Зачем помещать его в структуру, я рассказывал в статье, посвященной wjson. Отмечу только, что здесь определения typedef-ов требуется, чтобы эти структуры были распознаны как json-описание.

Обработку JSON-RPC сообщений можно разделить на два этапа. На первом этапе нужно определить тип запроса и имя метода, а на втором десериализовать параметры для этого метода.

При таком описании в поля request::params и request::id json будет скопирован как есть, без какого-либо преобразования, а в поле request::method будет собственно имя метода. Определив имя метода, можем десериализовать параметры описанными выше структурами.

Чтобы определить имя метода, не обязательно десериализовать весь запрос в промежуточную структуру данных. Достаточно его распарсить, а десериализовать только кусок запроса, относящийся к полю params. Это можно сделать с помощью wjson::parser напрямую, но wjson предоставляет также конструкцию raw_pair (в предыдущей статье я ее не рассматривал), которая позволит не десериализовать элементы, а запомнить его расположение во входном буфере. Рассмотрим, как это реализовано в wjrpc.

Начнем с того, что wjrpc не работает со строками std::string, а определяет следующие типы:

Можно рассматривать data_ptr как перемещаемый буфер, использование которого позволяет нам гарантировать, что данные лишний раз не будут скопированы по недосмотру.

Любое входящее сообщение wjrpc попытается десериализовать в следующую структуру:

Здесь нет полноценной десериализации — это по сути парсинг с запоминанием позиций во входном буфере. Очевидно, что после такой десериализации данные будут валидны, только пока существует входной буфер.

Класс wjrpc::incoming_holder захватывает буфер с запросом и парсит его в описанную выше структуру. Я не буду подробно описывать его интерфейс, но покажу, как можно его использовать для реализации JSON-RPC.

Здесь большую часть кода занимает формирование ответа, потому что не делаем проверок на ошибки. Сначала инициализируем incoming_holder строкой и парсим ее. На этом этапе входная строка десериализуется в описанную выше структуру incoming. Если строка содержит любой валидный json-объект, то этот этап пройдет без ошибок.

Далее нужно определить тип запроса. Это легко сделать по наличию или отсутствию полей “method”, “result”, “error” и “id”.

Комбинация Тип сообщения Проверка Получить
method и id запрос is_request get_params<>
method без id уведомление is_notify get_params<>
result и id ответ на запрос is_response get_result<>
error и id ошибка в ответ на запрос is_request_error get_error<>
error без id прочие ошибки is_other_error get_error<>

Если не выполняется ни одно из условий, то очевидно, что запрос неправильный.

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

Для формирования сообщения об ошибке нам достаточно знать тип ошибки и идентификатор вызова, если он есть. Объект inholder перемещаемый и после формирования сообщения он больше не нужен. В примере он используется только для извлечения идентификатора вызова, но у него также можно “забрать” входной буфер — для сериализации туда сообщения, чтобы не создавать новый.

Аналогичным образом можно и реализовать сериализацию результата. Но прежде чем продолжить избавляться от однотипного кода, приведем в порядок прикладную часть, которая здесь несколько затерялась и представлена всего одной строкой для каждого метода.

Интерфейсы

Как я уже говорил, wjrpc выдран из фреймворка, в котором для компонентов нужно явно определять интерфейс. Причем, это не просто структура исключительно с чистыми виртуальными методами, но и предъявляются определенные ограничения к параметрам методов.

Все входные и выходные параметры должны быть объединены в структуры, даже если там будет только одно поле. Это удобно не только для формирования json-описания для запроса, когда десериализованную структуру можем передать напрямую в метод, без предварительного преобразования, но и с позиции расширяемости.

Например, во всех методах мы возвращаем число — результат выполнения операции. Смысл описывать результат структурой с одним полем, если можно числом? А входные параметры вообще можно было передать массивом (позиционными параметрами).

Но теперь представьте, что требования немного изменились, и к списку параметров нужно добавить сопроводительную информацию, либо в ответе, для операции деления, флаг, означающий, что произошло деление на ноль. В этом случае для внесения исправлений нужно “перетряхнуть” не только интерфейс и все его реализации, но и все места, где он используется.

Если у нас JSON-RPC сервис, то изменения коснутся не только сервера, но и клиента, интерфейсы которых нужно как-то согласовывать. А в случае со структурами достаточно просто добавить поле в структуру. Причем, обновлять клиентскую часть и серверную часть можно независимо.

Еще одна особенность фреймворка в том, что все методы имеют асинхронный интерфейс, т.е. результат возвращается не напрямую, а через функцию обратного вызова. А чтобы избежать ошибок непреднамеренного копирования, входной и выходной объект описывается как std::unique_ptr<>.

Для нашего калькулятора, с учетом описанных ограничений, получается вот такой интерфейс:

С учетом вспомогательных typedef-ов, которые мы определили во входных структурах, выглядит вполне симпатично. Но вот реализация для подобных интерфейсов может быть достаточно объемной относительно простых примеров. Нужно удостовериться, что входной запрос не nullptr, а также проверить функцию обратного вызова. Если она не определена, то очевидно, что это уведомление, и в данном случае вызов нужно просто проигнорировать. Этот функционал легко шаблонизировать, как показано в примере реализации нашего калькулятора

Статья все же не про реализацию калькулятора, поэтому представленный выше код не стоит воспринимать как best practice. Пример вызова метода:

К теме интерфейсов еще вернемся, а сейчас продолжим “сокращать” код нашего сервиса. Для того, чтобы асинхронно сериализовать результат, необходимо “захватить” входящий запрос

Т.к. incoming_holder перемещаемый, то чтобы его “захватить”, перемещаем его в std::shared_ptr. Здесь показано, как можно забрать у него буфер, но в данном случае это не имеет особого смысла — все равно результат помещаем в список строк. Захват res_list по ссылке — это только для примера, т.к. знаем, что запрос будет выполнен синхронно.

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

Здесь шаблонный параметр нужно указывать явно — это json-описание для структуры ответа, из которого можно взять тип описываемой структуры. С использованием этой функции код для каждого JSON-RPC метода существенно упростится

Для каждого метода код сократился до минимума, но и этого мне мало. Получение параметров и проверка на ошибку — тоже однотипный код.

Это уже почти так, как реализовано в wjrpc. В результате код демо примера сократится до минимума (здесь уже можно привести реализацию всех методов)

Такое маниакальное желание сократить run-time объем кода обусловлено несколькими причинами. Убирая лишний if с помощью достаточно сложной конструкции, мы не только сокращаем объем кода, но и убираем потенциальные места, где программисту будет в радость наговнокодить. А еще программисты любят копипастить, особенно неинтересный код, связанный сериализацией, размазывая его по всему проекту, а то и привнося в другие проекты. Лень — двигатель прогресса, когда она заставляет придумывать человека то, что позволит ему меньше работать. Но не в том случае, когда дела откладываются на потом. Казалось бы, тривиальная проверка, но со словами — это же пока прототип, написание пары строк кода откладывается, потом забывается и одновременно копипастится по всему проекту, а также подхватывается другими программистами.

На самом деле мы еще не начали рассматривать wjrpc. Я показал, как описываются запросы с помощью wjson, описал интерфейс и прикладную логику тестового примера, и рассмотрел, как можно было бы реализовать JSON-RPC вручную, чтобы было понимание, как он работает изнутри и почему. А вот полный пример на wjrpc:

С помощью JSONRPC_TAG задаются имена методов для передачи в качестве шаблонных параметров, аналогично JSONNAME в wjson, разница только в именах сущностей, которые вместо префикса n, обрамляются символами подчеркивания.

Далее с помощью wjrpc::method_list и wjrpc::invoke_method описываем все доступные методы. Список методов передаем в обработчик wjrpc::handler. Наряду с методами в списке был описан тип интерфейса объекта, с которым обработчик будет работать.

Основным методом обработки является perform_io, который работает с wjrpc::data_ptr.

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

Здесь handler обработчик ответа, который первым параметром принимает собственно ответ, а вторым сообщения об ошибке. А t — это ссылка на объект JSON-RPC-обработчика (по аналогии self в python). Включается он в список методов следующим образом:

Как можно догадаться, invoke_method<> это обертка для wjrpc::method<>, которая использует обработчик метода:

Мы долгое время в своих проектах не использовали асинхронные интерфейсы, да и сама реализация JSON-RPC была несколько проще, но для каждого метода приходилось писать вот такой обработчик, основная задача которого вызвать метод соответствующего класса прикладной логики и отправить ответ.

Но проблема таких обработчиков не в том, что они есть (а ведь можно и без них), а в том, что программисты часто (да почти всегда) начинают переносить туда часть прикладной логики. Абсолютно по разным причинам. Не проработан интерфейс, просто скопипастил откуда-то код, лень подумать и прочее. Зачастую на этапах прототипирования логика не намного сложнее нашего примера. Так зачем плодить сущности, продумывать инициализацию системы и пр? Пару-тройку синглетонов и впендюриваем логику прямо в обработчик. Тем более, не надо думать, как ошибку протаскивать. Потом все это обрастает кодом и потихоньку все забывают, что это прототип, и он уезжает в продакшн. Поддерживать такой код очень тяжело, невозможно покрыть тестами, проще переписать с нуля, чем отрефакторить подобный проект.

В какой-то момент пришла идея “обязать” использовать интерфейсы и стандартизировать их. Благодаря этому появилась возможность сделать обертку типа wjrpc::invoke_method. На этой концепции построен весь фреймворк, а не только JSON-RPC. Концепция заключается в максимальном ограничении возможностей для “творчества” программисту в слоях, которые не связаны с прикладной логикой.

Если вам не нужна асинхронность, то можно также проработать стандарт интерфейса и написать обертку типа wjrpc::invoke_method.

Объект wjrpc::handler<> обычно создается в контексте соединения и имеет такое же время жизни. Если callback, например, нужен объект соединения, чтобы отправить ответ, то нужно поставить защиту на случай, если вызов произойдет после уничтожения объекта.

Второй вариант не подходит, т.к. происходит захват объекта соединения на неопределенное время со всеми ресурсами. Вариант

уже лучше, но мы не можем повторно использовать такой объект (например, для пула объектов). Для идентификации объекта можно использовать умный указатель произвольного типа, который нужно просто пересоздать, чтобы отправленные гулять по системе callback-и стали больше не актуальны.

Для унификации подхода можно разработать вспомогательные классы (нет в wjrpc)

Но это еще не все проблемы с функциями обратного вызова. Они должны быть вызваны обязательно и вызваны ровно один раз. Я не буду сейчас на этом подробно останавливаться, просто имейте это в виду. Напомню, что эта библиотека вырвана из фреймворка, и там эти проблемы так или иначе решены. Связываться или нет с асинхронностью, решать вам.

JSON-RPC Engine

Движок wjrpc::engine – это, по сути, реестр jsonrpc-обработчиков, который позволяет вынести их в отдельный модуль и управлять временем жизни. Например, входящий поток сообщений можно распределить по очередям с разными приоритетами, исходя из имен методов, и только потом передать в wjrpc::engine. Также он используется для реализации удаленных вызовов к другим сервисам. Для входящих запросов объект соединения захватывается callback-ом, который может гулять неопределенно долго по системе, поэтому он не нужен. Но для исходящих запросов нужен wjrpc::engine, чтобы связать сущность, отправившую запрос с обработчиком, чтобы доставить ответ, когда он придет.

Для демонстрации удаленных вызовов сначала разработаем проксирующий объект, который также реализует интерфейс icalc. Это будет такой злобный прокси, который меняет входные и выходные значения для метода plus, инкрементируя их.

Как видно, прокси инициализируется указателем на интерфейс icalc, куда перенаправляет все запросы как есть, кроме метода plus, в котором реализована его “злобность”. Также производится проверка входящих запросов и игнорируются все уведомления и нулевые запросы.

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

Для того, чтобы отправить запрос на другой сервер, нам нужно его сериализовать, а для этого опишем JSON-RPC шлюз.

Список методов проще, чем для сервисов, ведь нам не нужно описывать обработчик, а только указать JSON-описания запросов и ответов. Но обработчик исходящих вызовов будет посложнее — в нем нужно будет реализовать интерфейс icalc:

Реализация каждого метода интерфейса однотипна — нужно вызвать метод call<> с тегом нужного метода, передать туда запрос, обработчик ответа и обработчик ошибки, если необходимо. Обработчик результата осядет в движке до тех пор, пока мы не сообщим ему ответ. Также можно указать время жизни, по истечении которого будет вызван callback с nullptr.

Если в качестве ответа пришла JSON-RPC ошибка, то в callback также будет передан nullptr, что для прикладного кода будет означать, что произошла ошибка, не связанная с прикладной логикой. Пытаться протащить JSON-RPC коды в прикладную часть не очень хорошая идея. Лучше дополнить структуры ответа соответствующими статусами, описывающими ошибки именно прикладной логики.

В качестве примера попробуем связать шлюз, сервис, прокси и собственно калькулятор следующим образом:

Так как наш прокси имеет интерфейс калькулятора, то мы можем с ним работать как с калькулятором, а то, что он делает с запросом, нам не особо важно. Прокси может перенаправить запрос к шлюзу, который также имеет интерфейс калькулятора, поэтому нам не важно, с каким объектом связан прокси. Это может быть еще один прокси, а можно выстроить из них целую цепочку неограниченной длины. Задача шлюза — сериализовать запрос, который мы можем передать по сети. Но в примере мы сразу передадим его на сервис, который его десериализует и, в данном случае, передаст еще одному прокси.

Второй прокси также его модифицирует и уже передает непосредственно калькулятору. Калькулятор его честно отрабатывает и вызывает callback, в котором второй прокси модифицирует ответ и вызывает callback, который пришел от сервиса, который его сериализует. Сериализованный ответ передается JSON-RPC движку, который находит по id вызова соответствующий JSON-RPC обработчик и вызывает его. В нем ответ десериализуется и вызывается callback первого прокси, в котором ответ еще раз модифицируется и передается в наш исходный callback, в котором мы выводим ответ на экран.

Первый прокси инкрементировал параметры запроса первый раз, поэтому получаем в JSON-RPC значения 2 и 3 вместо 1 и 2. Второй прокси их также инкрементирует, а потом инкрементирует результат, поэтому сервис отправляет значение 8. Первый прокси еще раз инкрементирует результат, поэтому финальное значение 9. Второй запрос отправляем напрямую в шлюз, минуя первый прокси, но он проходит через второй, а модификация для munus там не предусмотрена, поэтому в результате правильный ответ.

Все двузначные числа в этом примере — это уникальные идентификаторы сущностей, которые должны быть уникальны и, разумеется, их нужно генерировать, а не прописывать в коде напрямую, например, так:

Эти идентификаторы служат для связывания сущностей без прямых ссылок на них. Например, в этой строке:

Мы в JSON-RPC движке шлюза регистрируем обработчик, который должен отправить данные на сервер. Он должен работать с каким-то объектом, клиентом, для некоего сервера, через который мы можем отправить уже сериализованный запрос. Можно сделать несколько параллельных подключений к серверу и зарегистрировать их. Но вместо этого сериализованный запрос передается напрямую на сервис. А число 44 — это идентификатор некоего объекта сервера, который был создан при подключении клиента. Такие коннекты можно также регистрировать в service::engine_type, но если у нас нет встречных вызовов (когда сервер вызывает метод клиента, а такое в wjrpc тоже можно реализовать), то этого делать не обязательно.

Далее я хотел привести аналогичный пример с взаимодействием между процессами через пайпы(::pipe), с подробным описанием, но думаю, что я вас еще только больше запутаю, да и особо практического смысла это не имеет, а сделать более-менее реалистичный пример взаимодействия двух серверов, со всеми нюансами и чтобы они работали достаточно эффективно, потребует достаточно серьезной кодовой обвязки, к теме статьи отношения не имеющей. Пример построения цепочки процессов с взаимодействием через пайпы есть в разделе examples проекта, также, как и код остальных примеров, приведенных в этой статье.

Но если вы нашли удовлетворительной для себя концепцию сериализации wjson, про которую я писал в предыдущей статье, то в связке wjrpc::incoming_holder вполне можно сделать эффективный JSON-RPC сервер. Если не вызывает отторжения концепция асинхронных интерфейсов, то с помощью wjrpc::handler вы можете сделать все то же самое, но с меньшим количеством run-time кода.

Скачать wjrpc можно здесь. Вам также понадобятся faslib и wjson. Чтобы скомпилировать примеры и тесты:

Источник

Читайте также:  что делать если ухо заложило при насморке и не проходит болит у взрослого
Строительный портал