- PVSM.RU - https://www.pvsm.ru -
Если вы разработчик, и у вас есть проект на PHP, и ему наконец-то понадобилось реализовать собственное API — эта статья определенно для вас ;).
JSON-RPC [1] v1.0 появился в 2005 году, спустя 5 лет появилась и вторая версия [2]. В век javascript'а и мобильных приложений многие разработчики до сих пор используют свои собственные велосипеды вместо готового простого стандарта.
Попытаюсь выделить ключевые особенности:
Для тех, кто знаком с версией 1.0, значимыми нововведениями в 2.0 были именованные параметры и очередь вызовов.
Простой запрос/ответ выглядит следующим образом:
--> {"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"}
]
В этом сложном «творческом» мире программирования всегда встает вопрос: взять что-то готовое или написать что-то свое?
Как обычно, определим критерии к инструменту, который хочется найти/получить:
Для начала хватит.
Т.к. JSON-RPC достаточно молодой протокол, то в нем ещё есть моменты, которые до конца не утверждены. Один из них — Service Mapping Description [3], предложенный Dojo. SMD полностью может описать веб-сервис, начиная от его методов, заканчивая развернутыми возвращаемыми типами. К сожалению, очень мало решений поддерживает его реализацию, например Zend_Json_Server [4], inputEx framework (генерация формочек [5] для тестирования) и сам Dojo framework [6].
Перейдем к поиску существующих решений.
Список клиентов я взял из таблички в википедии [7].
php-json-rpc [8] | jsonrpc2php [9] | tivoka [10] | junior [11] | json-rpc-php [12] | JSONRpc2 [13] | Zend Json Server |
zoServices [14] | |
Сервер | ||||||||
Соответствие спецификации | - | Правильная поддержка Notification в Batch режиме. | + | Нет поддержки опциональных именованных параметров | + | + | + | Нет поддержки опциональных именованных параметров |
Кол-во файлов | - | 2 | >7 | 3 | 6 | 1 | >5 | 6 |
SMD-схема | - | - | - | - | - | - | + | - |
Тесты | - | + | - | + | - | + | + | - |
Внутрення реализация (1..5) | - | 4 Ручной маппинг экспортируемых функций |
3. Слишком сложная реализация для такой простой задачи | 4 | 4. Сложно | 4. Magic Inside! | 4+. Zend | 3. |
Клиент | ||||||||
Соответствие спецификации | Нет Batch и Notification | - | + | + | + | Нет Batch | - | Notificaiton отсутствуют |
Кол-во файлов | 1 | - | >7 | 4 | 2 | 1 | - | 6 |
Тесты | - | - | - | + | - | + | - | - |
Внутренняя реализация (1..5) | 4 | - | 3. Лишние шаги для вызова методов | 4 | 4 | 4+. :) | - | 3 |
Автоматическая генерация | - | - | - | - | - | - | - | - |
Умный читатель может возразить, какая разница, сколько файлов используется для реализации, все равно можно склеить все в один. Да, можно, но это дополнительное лишнее действие.
Как мы видим, нет «идеального» решения, которое бы нам подошло и которое можно спокойной использовать без напильника. Надеюсь, у других платформ дело обстоит гораздо лучше :)
Складывается общее ощущение недоделанности проектов, ни одно решение не может предложить полный цикл использования экспортируемого API (server, smd-schema, client-generation).
Ещё я не очень понимаю тех разработчиков, которые пытаются сделать из PHP, скажем Java или C#. В большинстве своем — PHP используется в схеме запрос/ответ, а не в схеме application server со своими состояниями. Скрипт — это же не скомпилированная закрытая библиотека.
Ответ на вопрос «использовать что-то готовое» или «написать свое» очевиден.
Проект на GitHub [15]. Все требования, обозначенные ранее, реализованы :)
Вариантов использования два: либо отнаследоваться от класса BaseJsonRpcServer, либо создать его экземпляр и передать в конструктор экспортируемый объект:
<?php
include 'BaseJsonRpcServer.php';
include 'tests/lib/DateTimeRpcService.php'; // тестовый пример
$server = new DateTimeRpcService(); // или new BaseJsonRpcServer( new DateTimeService() );
$server->Execute();
Таким образом, мы открыли наружу все public-методы класса DateTimeRpcService. SMD-схему можно получить через GET-параметр smd (например eazyjsonrpc/example-server.php?smd [16]). При построении схемы учитываются phpDoc-блоки.
{"transport":"POST","envelope":"JSON-RPC-2.0","SMDVersion":"2.0","contentType":"application/json","target":"/example-server.php","services":{"GetTime":{"parameters":[{"name":"timezone","optional":true,"type":"string","default":"UTC"},{"name":"format","optional":true,"type":"string","default":"c"}],"description":"Get Current Time","returns":{"type":"string"}},"GetTimeZones":{"parameters":[],"description":"Returns associative array containing dst, offset and the timezone name","returns":{"type":"array"}},"GetRelativeTime":{"parameters":[{"name":"text","optional":false,"type":"string","description":"a date/time stringr"},{"name":"timezone","optional":true,"type":"string","default":"UTC"},{"name":"format","optional":true,"type":"string","default":"c"}],"description":"Get Relative time","returns":{"type":"string"}},"Implode":{"parameters":[{"name":"glue","optional":false,"type":"string"},{"name":"pieces","optional":true,"type":"array","default":["1","2","3"]}],"description":"Implode Function","returns":{"type":"string","description":"string"}}},"description":"Simple Date Time Service"}
Вариантов использования опять же два: либо создать экземпляр класса BaseJsonRpcClient и передать ему ссылку веб-сервиса в конструкторе, либо воспользоваться генератором:
<?php
include 'BaseJsonRpcClient.php';
$client = new BaseJsonRpcClient( 'http://eazyjsonrpc/example-server.php' );
$result = $client->GetRelativeTime( 'yesterday' );
На основе SMD-схемы мы можем сгенерировать класс для работы с сервером (см. пример DateTimeServiceClient.php [17]). Для этого вызовем генератор:
php JsonRpcClientGenerator.php http://eazyjsonrpc/example-server.php?smd DateTimeServiceClient
Результатом выполнения команды будет файл DateTimeServiceClient.php с необходимыми нам методами.
Негласным правилом для вызова class->method() в JSON-RPC используется class.method в качестве имени метода (через точку).
В текущей реализации такой функциональности не предусмотрено. Предполагается, что url — это экспортируемый класс, тогда вариант с точками отпадает :). Касательно клиентской части — тут всегда можно дописать, это всего-лишь PHP.
Так же в SMD есть возможность описать возвращаемые типы в виде объектов с их свойствами, но в виду сложности реализации пока этот момент мы опустим.
Тем, кто хочет найти подробную документацию, могу предложить прочитать ещё раз названия методов, phpDoc комменты к ним и исходный код Server или Client.
Вариантов реализации несколько:
Некоторые люди предлагают странный вариант с кодированием файла в base64 и его отправкой в каком-то поле.
Более-менее нормальное решение заключается в реализации метода, который расскажет, по какому адресу можно начинать загружать файл.
--> {"jsonrpc": "2.0", "method": "send_image", "params": ..., "id": 1}
<-- {"jsonrpc": "2.0", "result": {"URL": "/exampleurl?id=12345", "maxsize": 10000000, "accepted-format":["jpg", "png"]}, "id": 1}
--> загрузим постом файл по заданному адресу.
ну и в конце можно проверить, все ли хорошо
--> {"jsonrpc": "2.0", "method": "send_done", "params": {"checksum": "1a5e8f13"}, "id": 2}
<-- {"jsonrpc": "2.0", "result": "ok"}
Сам протокол уже предусматривает наличие объекта error с полями code, message и data. При использовании BaseJsonRpcServer внутри вызываемого метода можно кинуть Exception, в котороый передать code и data. Свой message можно добавить в массив $errorMessages по определенному code.
Тут полностью все зависит от вас, как реализуете — так и будет. Могу лишь посоветовать создать какой-нибудь класс ObjectToJsonConverter, в котором реализовать преобразования объекта в требуемый массив.
<?php
class ObjectToJsonConverter {
/**
* @param City $city
* @return array
*/
public static function GetCity( City $city ) {
return array(
'id' => $city->cityId
, 'name' => $city->title
, 'region' => $city->region->title
);
}
}
// где-то в конце экспортируемого метода
return array_map( 'ObjectToJsonConverter::GetCity', $cities );
Тут опять же, все зависит только от вас. Например, можно создать требуемые классы и написать какой-нибудь простой конвертер обратно (идеи по конвертации можно взять в MyShowsClient.php [18])
Надеюсь, после прочтения статьи, JSON-RPC не будет обделен вниманием при выборе протокола взаимодействия.
Даже после покрытия тестами > 89% кода, я могу лишь сказать:«Вроде должно работать» :)
Автор: sergeyfast
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/14405
Ссылки в тексте:
[1] JSON-RPC: https://secure.wikimedia.org/wikipedia/en/wiki/JSON-RPC
[2] вторая версия: http://www.jsonrpc.org/specification
[3] Service Mapping Description: http://dojotoolkit.org/reference-guide/1.8/dojox/rpc/smd.html
[4] Zend_Json_Server: http://framework.zend.com/manual/ru/zend.json.server.html
[5] генерация формочек: http://neyric.github.com/inputex/examples/rpc/smdtester.html
[6] Dojo framework: http://dojotoolkit.org/reference-guide/1.8/dojox/rpc/SMDLibrary.html
[7] таблички в википедии: https://secure.wikimedia.org/wikipedia/en/wiki/JSON-RPC#Implementations
[8] php-json-rpc: https://bitbucket.org/jbg/php-json-rpc/src
[9] jsonrpc2php: https://github.com/mpcm/JSON-RPC-PHP
[10] tivoka: https://github.com/marcelklehr/tivoka
[11] junior: https://github.com/EvilScott/junior/
[12] json-rpc-php: https://github.com/Pozo/json-rpc-php
[13] JSONRpc2: https://github.com/foglcz/JSONRpc2
[14] zoServices: http://sourceforge.net/projects/jsonrpc20api/
[15] GitHub: https://github.com/sergeyfast/eazy-jsonrpc
[16] eazyjsonrpc/example-server.php?smd: http://eazyjsonrpc/example-server.php?smd
[17] DateTimeServiceClient.php: https://github.com/sergeyfast/eazy-jsonrpc/blob/master/tests/lib/DateTimeServiceClient.php
[18] MyShowsClient.php: http://api.myshows.ru/shared/samples/MyShowsClient.php
Нажмите здесь для печати.