https://habr.com/ru/post/446348/Вступление
Что такое JSON-RPC API? Это просто один из типов API, но ещё и чёткий стандарт, чего в этой статье может и не быть(да, будет самопис).
После того как я возился с RESTful API какое-то время и сильно на него разозлился, за то, насколько он прост снаружи и может быть сложен внутри, я полез в google на поиски замены.
И я наткнулся на статью о JSON-RPC API, и меня сильно заинтересовала его концепция, настолько, что я решил реализовать свой максимально простой велосипед.
Концепция JSON-RPC API
Главная идея сего стандарта в неком объекто-ориентированном подходе.
Семантически запрос выглядит так:
{
"api version": 0.1,
"method": "object.method",
"params": {
"user id": 1234
}
}
И это только один запрос, большая прелесть такого API в том, что их можно красиво объединять, и это даёт нам возможность использовать его для batch-запросов(загружать что-либо по частям).
То есть полный запрос может выглядеть вот таким образом.
{
"api_v": "0.1",
"reqs": [
{
"name": "my_important_request",
"method": "user.kick_out",
"params": {
"id": "1234",
"when": "now",
...
}
},
...
]
}
Здесь api_v — это версия API, а reqs — это массив запросов. Что важно, каждый запрос имеет метод(класс.метод), параметры и имя. Имя здесь играет большую роль. Когда вам пришёл ответ с сервера, вы можете обратиться к результату запроса по его имени. Пример: Запрос с методом добавлением юзера, следует назвать «user_creating», но это вам решать ;)
Начнём писать
Первое, что нужно сделать это класс API, в моём случае он делает даже меньше, чем должен. Некоторые процессы у меня находятся отдельно от него, но сути это не меняет.
<?php
// @package 'api_0.1.php'
// API версия 0.1
class API
{
private $last_resp; // Результат последнего запроса
private $resp = []; // Массив запросов
public function __call( $method, $params ) {
// Парсим запрос, берём класс и метод
$object = substr($method, 0, strpos($method, '.'));
$method_name = substr($method, strpos($method, '.')+1);
// Включаем класс
include_once __DIR__.'/source/'.$object.'.methods.php';
// Исполняем метод и сохраняем результат
$resp = $object::$method_name($params);
if(!empty($resp))
$this->last_resp = $resp;
else
$this->last_resp = null;
}
// Сохраняет результат последнего запроса в массив запросов с указанным ключом-именем
pulbic function add_resp($req_name){
if($this->last_resp === null) return false;
$req = $this->last_resp;
$this->resp[$req_name] = $req;
}
// Конечная функция, возвращает все результаты
public function response(){
exit ( json_encode($this->resp) );
}
}
В коде есть комментарии, но вот краткий экскурс… Мы вызываем у API неизвестную функцию, используем магическую функцию __call для этих целей. То есть вызывая функцию «Object.method» у API ($api->{«Object.method»}), он автоматически разбивает строку на пару объект-метод и вызывает его. После чего результаты всех запросов складываются в один массив и он в формате json отправляется назад. Всё просто.
Классы
Очень важно — здесь классы храняться в папке source и вот как должен выглядеть класс
<?php
class object{
function method($params){ /* ... */ }
}
Имя класса должно совпадать с тем, что запрашивается в запросе, тоже самое и с названием метода. Всё остальное неважно. Метод может делать что угодно и возвращать что угодно.
Но нам также нужен ещё и управляющий скрипт. Это тот самый скрипт, который будет вызываться при запросе.
<?php
// @package api.php
header('Content-Type: application/json'); // Устанавливаем тип данных на json
$api_v = $_POST['api_v']; //
$path = __DIR__.'/APIs/api_'.$api_v.'.php'; // Составляем путь к файлу и после проверяем, что он существует
if(!file_exists($path))
exit; // Здесь было бы правильнее вывести ошибку
include_once __DIR__.'/APIs/api_'.$api_v.'.php';
// Инициализируем API
$api = new API();
$reqs = $_POST['reqs'];
$reqs = @json_decode($reqs, true); // Конвертируем json в php массив (ассоциативный)
// Проходимся по каждому запросу, вызываем определенный метод и сохраняем его результат в массив API
foreach ($reqs as $req) {
$method = $req['method'];
$params = $req['params'];
$req_name = $req['name'];
$api->$method($params);
$api->add_resp($req_name);
}
// Возвращаем все результаты
$api->response();
Что же здесь происходит? Мы включаем наш API в зависимости от указанной в запросе версии. Декодируем json-запросы и проходимся по каждому из них. Вызываем у API метод типа «object.method» и сохраняем его результат под именем этого запроса(выше было написано, что каждый запрос имеет своё имя). После исполнения всех запросов мы возвращаем json-массив результатов… И, в принципе, всё.
Немного JS
Вот небольшой пример функции в js, которая будет делать API запросы такого типа. Написано, используя jQuery, и я дико перед вами извиняюсь за это, но так просто проще показать саму суть без лишнего.
function api_call(reqs, callback){
// Кодируем массив(или не массив, а только один запрос, тут массив всё равно создаётся) запросов в json
var json = JSON.stringify( (Array.isArray(reqs) ? reqs : [reqs]) );
// Делаем POST запрос
$.post({
url: '/api/api.php', // путь к файлу api.php
dataType: 'json', // Устанавливаем тип json, чтоб самим не декодировать
data: ({ api_v: '0.1', reqs: json }), // Отправляем версию API и запросы (в json формате)
success: function(res){
// Сохраняем каждый запрос в массив загруженных в контексте window, чтоб использовать их в любом месте. Это совсем не обязательно
for(var key in res){
window.loaded[key] = res[key];
}
// Исполняем функцию после удачного запроса
callback(res);
}
});
}
Здесь простой POST запрос, и почти нет ничего особенного, кроме того, что есть возможность указать только один запрос, а не массив, и также я сохраняю все ответы в массив загруженных, это просто так, для удобства, делать это совсем не обязательно.
Что ж, я надеюсь главная мысль вам понятна. Мне хотелось сделать простой и интуитивный API — я сделал. Я по большей части хотел показать вот такой простой подход к созданию многофункционального API.
До скорой встречи…