JSON-RPC

667
.
reaper
Давно я здесь ни с кем ничем не делился.
Вот решил написать что-то.
И это что-то оказалось реализацией JSON-RPC протокола.
Правда только серверной части.

https://github.com/Kilte/json-rpc

Юзается это так:

use Acme\UserApplication;
use Kilte\JsonRpc\Application;
use Kilte\JsonRpc\Server;
use Kilte\JsonRpc\Request\IOStreamFactory;
use Kilte\JsonRpc\Response\ResponseFactory;

$app = new Application(new UserApplication());

$responseFactory = new ResponseFactory();
$responseFactory->add('http', '\\Kilte\\JsonRpc\\Response\\HttpResponse');

$server = new Server($app, new IOStreamFactory(), $responseFactory, 'http');
$server->handle();


Первым делом создаём экземпляр приложения.
Для этого воспользуемся объектом класса pplication, который выступает в качестве обертки для пользовательских методов.
Туда можно запихнуть как и экземпляр класс, так и массив с анонимными функциями.

Затем создаем экземпляр класса ResponseFactory для того, чтобы определить, каким образом сервер будет отвечать клиенту.
В данном случает он будет отвечать по http.

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

IOStreamFactory отвечает за создание объекта запроса. Данные берет из php://input.
Можно определить свою фабрику унаследовавшись от AbstractFactory.

Также, как вы уже наверное догадались, вместо http-ответа можно тоже определить что-либо своё для того, чтобы заюзать, zeromq или WebSockets к примеру, а может вы юзаете какой-то фреймворк, в котором не принято обращаться к глобальным переменным и есть свои методы/объекты доступа к ним (Symfony к примеру).
Для того, чтобы указать откуда брать тело запроса нужно реализовать ResponseInterface и закинуть его в фабрику. А еще указать серваку, что нужно юзать именно его. (Последний аргумент у Server в примере).

Осталось реализовать обработку batch вызовов и еще раз всё перепроверить.
Ну и заодно доку запилить.

Либа протестирована на 99%, так что всё должно быть в порядке и можно юзать уже сейчас. Но я не стал бы рисковать и подождал первого релиза, дабы потом не пришлось всё переделывать, если мне вдруг приспичит что-то переделать так, что оно не будет иметь обратную совместимость с текущей версией.
.
Доделал таки. Уже наверное сейчас даже зарелизю.
Избавился от ненужной фабрики ответов.
Теперь просто реализуешь ResponseInterface и инжектишь в сервер.
Ну и тест соответствия спеке написал.
Вот только расширения как и все остальные, кто писал подобное, проигнорил.
В спеке говорится о том, что если метод начинается с "rpc.", то он должен использоваться только в экстеншене и ни для чего больше. Я посмотрел, как в таком случае поступили в других либах. Даже никакого намека на это не увидел. Ну и похер гг.
Написал пример, в котором в качестве транспорта юзается tcp (с помощью zmq). Прикольно Гг.
Прикрепленные файлы:
.
(\/)____o_O____(\/)
reaper, это односторонняя связь я так понял? сервер с данными ни чего делает?
.
Koenig, Как это не делает ? Ты вызываешь метод, оопционально передаешь аргументы, получаешь результат. RPC - remote procedure call - вызов удаленной процедуры. Ты можешь делать что угодно. Это как если бы ты в скрипте вызвал какую нибудь функцию, только это удаленно. еще можно отправить уведомление, тогда сервер ничего отвечать не будет.
.
(\/)____o_O____(\/)
reaper, не совсем понял, вызов процедуры удаленно, типо как протокол управления? дал команду с аргументами и все поехало? сервер только ответ отправляет?
.
(\/)____o_O____(\/)
то есть по сути можно фтп приложение слепить из этого?
.
Koenig, Короче пример Гг:
Запрос от клиента:
{"jsonrpc": "2.0", ",method": "update_password", "params": {"old": "1234", "new": "12345", "confirm": "12345"}, "id": 1}
Ответ сервера:
{"jsonrpc": "2.0", "result": "Password updated", "id": 1}

Запрос к примеру шлешь js'ом, а обрабатываешь его уже на сервере похапешным приложением.
Или если нужно сделать приватное апи, то шлешь запрос похапешным клиентом, и похапешным же сервером его обрабатываешь. Здесь уже даже можно в качестве транспорта кроме http какой-нибудь zmq заюзать, чтобы было круче Гг. А аутентификацию уже как-нибудь сам придумаешь, как запилить. Мне пока лень думать в этом направлении.

Словом, его можно заюзать где угодно и как угодно. Очень пригождается, если пишешь какую-нибудь распределенную фигню. Или если не хочешь осиливать REST, то это тоже как раз для тебя.
.
Обновил либу. Появилось несколько важных улучшений. Без поломки обратной совместимости не обошлось, потому следующая версия будет 1.0.0

Удален ResponseInterface, теперь метод handle() сервера возвращает json строку или null, если пришло уведомление. Стало гораздо удобнее. Без этого невозможно было ранее засунуть ответ к примеру в симфониевский Response или какой-либо другой.

Улучшена обработка ошибок. Столкнулся со странным багом, когда каким-то образом не отлавливались некоторые исключения в методе handle сервера. Причем наблюдался он только в связке с симфонией. Я так и не понял в чём конкретно причина такого поведения, но проблема была решена.

Добавлена поддержка пространств имен.
Пример для наглядности:
$app = new Application(['namespace' => new UserApplication()]);
// Client: {"jsonrpc": "2.0", "method": "namespace.method", "params": [1, 2, 3], "id": 1}

Это позволит писать более сложные приложения с большим кол-вом методов. Ранее приходилось бы делать что-то вроде этого:
$userApp = new UserApplication();
$app = new Application(['namespace.method' => [$userApp, 'method'], // и т.д.]);

Что не очень красиво и жутко неудобно.

А еще можно свободно писать вот так:
$userApp = new UserApplication();
$app = new Application(['namespace.method' => [$userApp, 'dot.separated']]);

И метод в UserApplication будет dotSeparated или dotseparated, или Dotseparated, да как угодно. суть в том, что точки просто игнорируются, что придаёт больше гибкости.
.
(\/)____o_O____(\/)
reaper, с игнорированием точки прикола не понял, что мешает сразу верно написать?
.
Koenig, Есть методы sigIn, signOut, signUp. В js-е это будет как-то так:
$http.jsonrpc('user.signIn', {username: 'user', password: 'pass'});
$http.jsonrpc('user.signOut', {}, null);
$http.jsonrpc('user.signUp', {username: 'user', password: 'pass', confirm: 'pass'});

Так писать как-то неприятно и хотелось бы написать вот так:
$http.jsonrpc('user.sign.in', {username: 'user', password: 'pass'});
$http.jsonrpc('user.sign.out', {}, null);
$http.jsonrpc('user.sign.up', {username: 'user', password: 'pass', confirm: 'pass'});

Своего рода псевдо-неймспейсы. Ну это уже приятные мелочи.
Всего: 21