Что же такое JSON API ?


Наверняка многие в курсе.

JSON — Текстовый формат обмена данными JSON
API — Программный интерфейс приложения API

Ключевые слова здесь: интерфейс обмена данными.

A, что же тогда JSON-RPC?



JSON — мы уже в курсе.

RPC — удаленный вызов процедур RPC

Приходим к выводу, что JSON-RPC это: удаленный обмен данными.

Наверняка этот обмен данными будет происходить с неким интерфейсом, т.е. с API.

И в чем проблема?! Спросите Вы. А в том, что некоторые программисты разрабатывая JSON API, т.е интерфейс, забывают про JSON-RPC.И начинается очередное изобретение велосипеда. Frontend программист говорит: «я передам тебе такой то json», а Backend программист отвечает: «а я тебе верну такой то json». И все бы ничего, но было бы хорошо вспомнить о том, что умные люди уже давно разработали стандарты, вернее протоколы обмена данными. И не какие то сверх сложные, а очень даже простые: JSON-RPC

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

Главные отличия от существующих протоколов это:


  • Опциональный параметр «sign» — Signature (подпись) или Token
  • В запросах вместо параметра «param» используется параметр «data», т.к. мы всегда отправляем данные, а не просто параметры.
  • Во всех ответах всегда возвращается параметр «result» и в нем находится описание результата запроса «success» или «error».
  • Все данные в ответах приходят в параметре «data»
  • Можно использовать алиасы для названия параметров запроса и ответа

Может показаться. что отличия незначительные, но они принципиально важные.
Кстати, данный протокол появился на практике, т.е. создавая json api я использовал подход описанный в этом протоколе.

PS:


Получив кучу отрицательных комментариев и минусов, решил еще раз проверить, может я действительно, что то не так делаю? Естественно, всё что я здесь пишу, это мое личное мнение и я никому ничего не навязываю. Приведу пару примеров:
1. Пример запроса JSON API Yandex директ:
{
    "method": "GetClientInfo",
    "param": ["agrom"],
    "locale": "ru",
    "token": "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"
}

У них же можно почитать и про токены: Авторизационные токены

2. Пример из Сбербанк API Оплата из мобильного приложения с использованием Apple Pay
JSON запрос приводить не буду, он большой, по ссылке можно посмотреть.
Важно, что JSON запрос содержит «paymentToken». Вот ссылка на требования к формированию токена от Apple

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

Комментарии (23)


  1. robert_ayrapetyan
    12.11.2018 02:10

    Непонятно зачем это нужно. Существующие стандарты JSON-RPC и так гибкие дальше некуда. Передавайте sign параметром, кто мешает. Переименовали param -> data, назвали это новым стандартом… Такое себе. Приведите хоть один реальный пример, где ваш «стандарт» окажется лучше существующего 2.0 и поясните в чем именно будет преимущество.


    1. oka_volga Автор
      12.11.2018 11:08
      -1

      Передавайте sign параметром, кто мешает.

      Мешает стандарт.
      в стандарте предусмотрено 4 параметра:
      {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}

      и «sign» можно добавит только в «params», но в параметрах должны быть данные и помещать туда подпись можно но, мне кажется, не совсем логично.

      Переименовали param -> data
      Опять, же с точки зрения логики. мы передаем не просто параметры. а данные т.е. data и принимаем не просто результат, а данные т.е. data.

      А понятие результат сводится к получению «флага» успех или ошибка и мы будем обрабатывать полученные данные в зависимости от «флага».

      т.е. при стандартном подходе логика обработки ответа примерно такая:
      вариант 1 success
      1. получили ответ
      2. есть ли параметр result
      3. обрабатываем result

      вариант 2 error
      1. получили ответ
      2. есть ли параметр result
      3. если нет — ищем параметр error
      4. если есть error — обрабатываем

      При моем подходе. примерно так:
      вариант 1 success
      1. получили ответ
      2. есть ли параметр result и он == success
      3. обрабатываем data. если нужно

      вариант 2 error
      1. получили ответ
      2. есть ли параметр result и он == error
      3. обрабатываем data

      Мне кажется, что мой вариант немного логичней, но это не точно)

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

      Хотя мои сомнения появились когда я начал делать проект с использованием микро-сервисной архитектуры. А для обмена данными между сервисами есть 2 основных варианта
      RPC или Message Bus. Я как раз остановился на RPC. Но может нужен другой стандарт?
      Типа: Remote Service Call (Удаленный вызов сервиса)?

      Тогда мое творчество можно назвать по другому, типа: JSON-RPS Version 1.0 )


      1. mwizard
        12.11.2018 16:15

        А зачем нужен sign? Если это для защиты целостности сообщений, то как бы почему не общепринятый TLS? Если же это для JWT-токена, то почему не общепринятый `Authorization: Bearer`?


        1. oka_volga Автор
          12.11.2018 16:32

          А зачем нужен sign? Если это для защиты целостности сообщений, то как бы почему не общепринятый TLS? Если же это для JWT-токена, то почему не общепринятый `Authorization: Bearer`?

          TLS не имеет отношения к RPC, это шифрование протокола, а мы наоборот от протокола передачи данных абстрагируемся.
          JWT вещь отличная, но это это открытый стандарт (RFC 7519) для создания токенов доступа и к RPC тоже никак не относится.

          Токен в конечном итоге это некая хешированная строка, как вы ее создадите, по каким стандартам не важно. А передавать то этот токен как то нужно. Для этого параметр «sign», для передачи токен строки или нескольких строк.


          1. mwizard
            12.11.2018 16:35

            Вы так и не ответили, зачем он нужен. Если этот sign не нужен, то и передавать его нет смысла. Тем более, вы «абстрагируетесь от протокола передачи данных», но тем не менее, навязываете какое-то дополнительное невнятное поле, задачи которого с головой покрываются транспортными средствами.

            Или это все же про «подпись» md5(«super»+args+«secret») в духе flash-игр начала 2000-х?


            1. oka_volga Автор
              12.11.2018 18:58

              md5(«super»+args+«secret») в духе flash-игр начала 2000-х?

              Возможно) или можно
              hash('sha256', $json),
              или можно передать JWT token, это же просто строка

              Пример готового JWT:

              eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.EkN-DOsnsuRjRO6BxXemmJDm3HbxrbRzXglbN2S4sOkopdU4IsDxTI8jO19W_A4K8ZPJijNLis4EZsHeY559a4DFOd50_OqgHGuERTqYZyuhtF39yxJPAjUESwxk2J5k_4zM3O-vtd1Ghyo4IbqKKSy6J9mTniYJPenn5-HIirE

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

              навязываете какое-то дополнительное невнятное поле

              Поле не обязательное.


              1. mwizard
                12.11.2018 19:01

                Сорян, вы говорите глупости. Для «авторизированности» запроса токен передается в хедере Authorization, для чего он и был создан. Для проверки целостности запроса и идентификации отправителя используйте TLS, для чего он и был создан.

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


                1. oka_volga Автор
                  12.11.2018 20:53

                  Честно говоря не ожидал такой агрессии и непонимания(
                  Хотел уже было пуститься в аргументированный спор, но не вижу смысла.

                  Вы для себя все решили. Имеете право.


                  1. gdsmiler
                    13.11.2018 12:11

                    Ну нет, давайте все таки Вашу аргументацию. Интересно все таки, чем вам не подходят существующие механизмы, указанные предыдущим комментатором


                    1. oka_volga Автор
                      13.11.2018 12:45

                      Всё устраивает)

                      Все

                      существующие механизмы
                      для чего то предназначены и используются по назначению.

                      Что бы завершить эти не конструктивные споры, нашел сегодня пару примеров,
                      как Yandex и Сбербанк используют токены в своих JSON API вы при желании можете найти еще множество примеров.
                      Дополнил этими примерами свою статью, смотрите после «PS:»

                      Повторюсь:
                      Важно понимать, что токены и подписи в API используются часто, естественно на ряду с другими методами защиты. И те кто работает с разного рода API, прекрасно это знают.


      1. oka_volga Автор
        12.11.2018 16:21

        Тогда мое творчество можно назвать по другому, типа: JSON-RPS Version 1.0 )

        имел в виду JSON-RSC (Remote Service Call)


  1. mwizard
    12.11.2018 02:28

    Спасибо, это не нужно, закапывайте.


  1. SDKiller
    12.11.2018 04:57

    Алилуйя, вы изобрели велосипед.


    1. SDKiller
      12.11.2018 04:57

      jsonapi.org


  1. r1000ru
    12.11.2018 09:00
    +1

    Тут есть два момента — RPC (remote procedure call), оно же удаленный вызов процедуры. С каких-то древнейших времен повелось, что у процедуры могут быть «параметры» и она возвращает «результат».

    В вашей реализации, где в result вы передаете success или ошибку, я не могу результат мапить на конструктор объекта, его использующий. А в jsonrpc 2.0 — могу смело. Наличие result — гарантирует что результат получен. Наличие error — гарантирует что произошла ошибка. Одновременно их быть не может. К тому же наличие error.code позволит мне сделать локализацию, а добавление error.data — позволит подробно описать условия выполнения ошибки, для дальнейшей отладки. В зависимости от логики обработки, мне ничто не мешает передавать session_id (к примеру) как параметр или какой-нибудь X-Auth-Token в заголовке.


    1. oka_volga Автор
      12.11.2018 09:55
      -1

      С каких-то древнейших времен повелось, что у процедуры могут быть «параметры» и она возвращает «результат».


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

      result вы передаете success или ошибку, я не могу результат мапить

      в этом и смысл. Хотите мапить result, а его там нет, там error. Естественно, это все проверяется. Но мне показалось логичным, в начале понять, что нужно сделать, т.е.

      result = success — обрабатываем данные из data
      result = error — реагируем на ошибку, а все данные о ошибке в data

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

      мне ничто не мешает передавать session_id (к примеру) как параметр или какой-нибудь X-Auth-Token в заголовке.

      да, согласен. но если «общаются» 2 сервера и нужно абстрагироваться от канала?
      т.е. мы не знаем, что за канал передачи данных и о заголовках тоже ничего не знаем. А идентифицировать запросы, а в ряде случаев и ответы нужно.


      1. r1000ru
        12.11.2018 10:31

        Ответ, ошибка и отсутствие ответа — это результат запроса. А поле result по спецификации jsonrpc 2.0 — это результат выполнения вызываемой процедуры. Если процедуру выполнить успешно не удалось — результата нет и есть ошибка. Впрочем ваш вариант тоже имеет право на жизнь, но я от такой схемы отказался, мне проще проверить наличие свойства error, а не его значение. И при его отсутствии — вызвать обработчик результата. В противном случае — обработчик ошибки.

        JsonRPC 2.0 позволяет идентифицировать запросы и ответы по идентификатору. Ничто не мешает передавать идентификатор сессии в params — так будет работать везде, хоть через HTTP, хоть через брокеры сообщений/запросов (я использую NATS — очень надежный механизм, рекомендую).


        1. oka_volga Автор
          12.11.2018 11:35

          Согласен,

          Ничто не мешает передавать идентификатор сессии в params

          писал, по этому поводу чуть выше:
          «sign» можно добавит только в «params», но в параметрах должны быть данные и помещать туда подпись можно но, мне кажется, не совсем логично.

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


          1. oka_volga Автор
            12.11.2018 11:36

            Впрочем ваш вариант тоже имеет право на жизнь,

            Спасибо! И я считаю, что на вкус и цвет…


          1. r1000ru
            12.11.2018 12:17

            А вот в этом заключается очень частый просчет разработчиков различных API. Часто делают так (простой пример обновления профиля пользователя):


            { 
               "session_id": "fc94bb40-b938-409e-9855-3dc283680a0a",
               "password": "verysecretpassword",
               "first_name": "Vasia",
              "last_name": "pupkin",
              "birth_day": "2000-01-01"
            }

            Что и приводит к необходимости очищать данные от сессии и пароля, после их проверки.


            Если запрос построить так:


            { 
               "session_id": "fc94bb40-b938-409e-9855-3dc283680a0a",
               "password": "verysecretpassword",
               "person": {
                   "first_name": "Vasia",
                   "last_name": "pupkin",
                   "birth_day": "2000-01-01"
                }
            }

            то на каждый шаг требуется только одно свойство:


            1. Проверяем валидность сессии по session_id (и получаем идентификатор пользователя)
            2. Проверяем пароль по password и идентификатору профиля
            3. Вызываем обновление данных профиля по содержимому profile
              В любой момент мы можем прекратить действия.


          1. oka_volga Автор
            12.11.2018 12:51

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

            Нет, это не аргумент, отвечаю сам себе)))
            т.к. не важно где находится параметр «sign», к моменту его поиска в запросе, JSON уже должен быть разобран(


  1. dmitry_dvm
    12.11.2018 10:25

    Для себя раз и навсегда решил сделать так: всегда приходят data, issuccess и errors. Issuccess зависит тупо от содержимого errors (issuccess => errors==null). В errors есть code, message и description. Сам ответ всегда одинаковой структуры и его спокойно можно десериализовать в объект на клиенте с любой типизацией. Ориентировался на дотнет. Все это дарует неисчислимые блага на клиенте и унификацию апи, все счастливы. И никакого гемора с разбором ответов, когда то есть данные, то их нет.


    1. oka_volga Автор
      12.11.2018 11:12

      Я так понял, что вы предпочитаете подход похожий на мой