Comments 26
Прям ностальгия. В 2005-м писал свою реализацию и в нескольких проектах она до сих пор используется.
Особенности json-rpc:
— асинхронный, причём в одном соединении в обработке может висеть несколько запросов
— работа в режиме запрос->ответ, либо подписка->уведомления, причём подписываться может как клиент, так и сервер
Про подписку сервером — это больше похоже на версию 1.0, когда он был peer-to-peer ориентированный, разве нет?
UFO landed and left these words here
Я так и не понял чем плох REST. Не вижу разницы между:
host.ru/api + {«jsonrpc»: «2.0», «method»: «sum», «params»: [1,2,4], «id»: «1»}
и
get: host.ru/sum/1.json + {«params»:[1,2,4]}

И если вы изначально пишите RESTfull приложение вопрос об изобретении API над рабочим приложением отпадает сам собой. Тестировать вам приходится только свое приложение и писать изменения только в приложение. И курить левую документацию к левой софтине нет никакой необходимости.

Вы же предлагаете следующий сценарий:
1. Подключить API
2. обложить тестами API
Когда требуются изменения:
3. Писать тесты для app
4. Писать код в app (или наоборот)
5. Писать тесты для API
6. Писать код в API (или наоборот)

Когда в классической схеме RESTfull
1. пишем тест
2. пишем код
(или наоборот)

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

В чем отличие от REST: наличие какого-то стандартизированного механизма auto-discovery.

Предположим, что у нас есть уже веб-сервис и сгенерированный клиент на основе smd-схемы.
В веб-сервисе появились новые методы. В случае REST — нужно вручную написать к ним реализацию в клиенте.
В случае с auto-discovery — нужно всего-лишь сгенерировать нового клиента — профит.

Курить левую документацию не нужно, т.к. это JSON-RPC :)
Немного дополню, если в проекте используется сваггер, и проект построен по REST, то слой API и DTO модели можно также сгенерить github.com/swagger-api/swagger-codegen

Чет поздно на дату статьи посмотрел :)
В своем проекте (трекер, ссылка в профиле), использую Zend_Json_Server, но сам протокол немного модифицировал.

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

Вообщем, лучше объясню наглядно. Стандарт json-rpc подразумевает такие запросы-ответы:

тут я ваш пример скопипастил
--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}
<-- {"jsonrpc": "2.0", "result": 19, "id": 3}

--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": 19, "id": 1}

--> [
        {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
        {"jsonrpc": "2.0", "method": "foobar", "id": "2"}
    ]
<-- [
        {"jsonrpc": "2.0", "result": 7, "id": "1"},
        {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found."}, "id": "2"}
    ]



Я же сделал так:
1.
--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}
<-- {"result":[{"method":"object.method","params":555},{"method":"object2.method5","params":[1,2,3,4]}],"id":"1","jsonrpc":"2.0"}

2.
--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": [{"method":"object.method","params":555}, {"aaa":18}], "id": 1}

3.
--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": 19, "id": 1}



Поясню что тут происходит:
вызов такой (у меня плагин для jquery):
$.jsonrpc(method, params, callback, options);

Здесь
method — «Class.Method». На сервере происходят свои обрабоки, для того, чтобы вызывать только те методы классов, которые можно.
params — ну тут понятно.
callback — callback-функция, о ней ниже
options — опции, характерные для $.ajax

1. При ответе должны вызваться методы object.method(555) и object2.method([1,2,3,4])
2. При ответе вызывается метод object.method(555) и callback функция callback({"aaa".18})
3. При ответе вызывается callback(18);

Лично мне — очень удобно!

Думал написать статью, чтобы расписать все подробно, но времени нету. Так что сжато написал в комментарии :)
Мысль понятна, но вам не кажется, что сервер не должен знать. что происходит на клиенте в общем случае? :)
Мы с моим коллегой по предыдущему проекту очень много спорили об этом. Он, кстати, написал свою реализацию (довольно крутую) json-rpc для друпала. Он тоже скептически отнесся к моей реализации :)

Но, удобство, реально очевидное.
Например, чат.
Пользователь посылает текст в чат. И получает ответ, в котором перечислены следующие методы:
1. сообщение добавлено
2. Надо вывести 2 новых собщения в чат
3. Онлайн изменился
4. что-нибудь ещё.

Т.е. фронтенд-программист просто пишет эти методы. А бекенд — их вызывает. Круто :)

В целом, в моем плагине есть возможность им пользоваться в обычном асинхронном режиме — один запрос, один ответ в колбек. Но, я, использую только эту свою реализацию, уж ооочень мне удобно с ней работать.
Изящно.
* auto-discovery — захватывает все public методы класса? куда девать public методы не используемые в API?
* mass-assignment?
>* auto-discovery — захватывает все public методы класса? куда девать public методы не используемые в API?
Вообще не делать их public) ну или добавить в $hiddenMethods, благо protected.
>* mass-assignment?
Тут не применимо, сигнатуры методов известны:)
А насколько возможно и без костылей за один такой JSON-RPC запрос в этой реализации передать несколько методов разных классов со своими параметрами и от всех их получить единый ответ?
Сделать общий proxy-класс, в котором описать все эти методы и сделать из него RPC Server.
Казалось бы, костыль. Но! Предположим, что мы делаем апи для мобильного приложения. Набор методов уже заранее известен, они находятся в разных классах. Создаем MobileAppServer (отнаследованный от BaseJsonRpcServer), в котором собираем все методы воедино — ничего лишнего. В такой реализации доступна пост-обработка результатов этих методов, например сокрытие лишних переменных в результирующих объектах. Как-то так.
В общем, через дополнительный слой. Меня интересовал вопрос, возможно ли это силами «чистого» самого JSON-RPC.
Используется в одном месте велосипедная реализация, где в одном запросе могут приходить десятки действий.
В Зенде проблема еще, что для SMD не вытаскивает комментарий/описание метода, а это зачастую важно. Также неясно поведение, когда серверные методы реализуются множеством классов — если там есть одинаковые методы, будет использован только последний. Нет возможности использовать что-то типа неймспейсов (на базе имен классов) или переопределять название метода. В результате пришлось писать свою костыльную реализацию :)
Спасибо за статью!
Я тоже одно время присматривался к этому способу создания API (делал на Python/Django и php) для обмена данными на смену SOAP. Но основной вопрос возник — как сделать кроссплатформенную авторизацию, чтобы сервер был допустим на Django, а клиент на php?
В простейшем случае конечно можно сделать авторизацию через веб-сервер и передавать логин и пароль через url, но хотелось бы что-то более серьезное.
Подскажите идеи.
Использовать HTTP Basic Auth. В клиенте достаточно добавить логин и пароль в массив $CurlOptions :)
Использовать токены через HTTP-заголовки. Для получения токенов можно написать необходимый метод.
Использовать токены как параметры метода.

помимо этих идей или их раскрыть надо?
Спасибо.
Да, похожие идеи были, но из этого списка только первый вариант относительно стандартным можно считать.
Для 2 и 3 придется существенно допиливать библиотеки и если для себя это нормально, то для организации обмена данными с чужими системами уже создает неудобство — им тоже придется допиливать свои библиотеки.
если делать запрос через метод call (не магический __call, а обычный), то запросу не назначается id. Из-за этого processCall($call) возвращает null.
Да, все верно. Если использовать call напрямую, то нужно дополнительно передать id, иначе не сделать реализацию с notifications (возможность передачи пустого id)
Only those users with full accounts are able to leave comments. Log in, please.