Привет, Хабр! На связи хостинг-провайдер SpaceWeb. В этой статье расскажем, как работает клиентский API у нас, почему для него мы выбрали технологию JSON-RPC и чем она нам так нравится. Историей делятся Виталий Киреев, руководитель R&D, и Алексей Шашкин, продакт-менеджер.

Бэкграунд проекта: API как панель управления 

Мы начали развивать API для наших продуктов еще 6 лет назад. Тогда у нас была задача сделать панель управления, которая отвечала бы современным стандартам: мобильное приложение, SPA. Но реализовать это «в лоб» было сложно, потому что у нас был большой legacy-проект — монолит с парадигмой MVC. Все инструменты внутри были очень связаны, правки было вносить практически невозможно. 

Тогда мы решили пойти по другому пути: полностью отделить черный ящик, который работает со всеми нашими сервисами, дать ему внешнее API и подключать к нему клиентов. Такой подход позже помог нам из панели управления выделить отдельный клиентский инструмент — API.

Архитектура проекта была изначально заложена правильно: бэкенд-слой был выделен отдельно и не связан с фронтенд-частью, а значит и клиент мог быть абсолютно любым. Для того, чтобы вызвать API, можно использовать разные транспорты: и HTTP, и брокер сообщений на RabbitMQ. И каждый слой сервисов имеет эффективное покрытие юнит-тестами. 

API для клиентов

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

За основу взяли API для панели управления. При этом мы понимали, что какие-то методы нужно зарефакторить и изменить по количеству параметров, потому что они могут быть не очевидны для клиента. Например, в API панели управления есть возвращаемые значения, которые нужны для отображения значений. А какому-то разработчику они будут не нужны. 

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

Какую технологию выбрали для API SpaceWeb

Для доступа к API используется протокол HTTP/HTTPS, а данные передаются в виде JSON-RPC по методу POST. Вызываемый метод API содержится в запросе вместе с параметрами вызова. 

Мы выбрали именно JSON-RPC, потому что было важно отделить слой бизнес-логики от слоя протокола. Его преимущество в том, что заголовки ответов читаются однозначно для нас и для клиента. Например, если пришел ответ 500, то это значит это проблема на серверной стороне. Если ответ пришел 200, но внутри ответа содержится расшифровка JSON-RPC ошибки, значит эта ошибка в приложении, слой бизнес-логики. 

А вот в Rest API все работает не так. Если возникает ошибка, то может приходить заголовок с кодом 500 и нам будет непонятно: это сам сервер упал или ответ от API с ошибкой 500. То есть здесь смешивается логика транспорта и приложения.

Как работает клиентский API 

Авторизация и токен

В большинстве случаев для доступа к API потребует токен. Получить его можно после авторизации, отправив запрос на специальный url: https://api.sweb.ru/notAuthorized/. Выглядит этот запрос вот так:

{
  "jsonrpc": "2.0",
  "method": "getToken",
  "params": {
    	"login": "<ваш логин>",
	"password": "<ваш пароль>"
  }
}

Сам токен будет содержаться в ответе в параметре result:

{
  "jsonrpc": "2.0",
  "id": "20220505104244.40FxsQ16Ff",
  "result": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Запрос

Для доступа к API запрашивается отдельный url: https://api.sweb.ru/domains/. При этом запрос содержит следующие заголовки:

Content-Type: application/json; charset=utf-8
Accept: application/json
Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Все запросы, кроме тех, которые адресованы к общедоступным методам, должны содержать токен авторизации Authorization: Bearer.

Тело запроса может иметь вид:

{
  "jsonrpc": "2.0",
  "method": "move",
  "params": {
    "domain": "vpstest.ru"
  },
  "id": "20183994338.43VSEkfGFh",
  "user": "xxxxxxxxx"
}

В этом запросе имеются следующие параметры:

  • jsonrpc — текущая версия JSON-RPC. Это обязательный параметр.

  • method — метод объекта Domains. Если он не передан, то система назначит метод объекта по умолчанию.

  • params — объект параметров метода (ключ элемента объекта - имя параметра).

  • id — уникальный идентификатор запроса. Если он в запросе не содержится, то id будет сформирован на стороне API.

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

Ответ

Если запрос к API отработан успешно, то система возвращает результат в виде JSON:

{
  "jsonrpc": "2.0",
  "id": "20183995523.MO4E9baKRr",
  "result": {
    "balance": {
      "real_balance": 500,
      "bonus_balance": 0
	}
  }
}

В ответе содержатся обязательные параметры:

  • jsonrpc — текущая версия JSON-RPC;

  • version — текущая версия клиента;

  • result — результат, который возвращает вызванный метод;

  • id — уникальный идентификатор ответа, если был в запросе, то совпадает с ним.

Если при обработке запроса возникает ошибка, то форма ответа будет такой:

{
  "jsonrpc": "2.0",
  "version": "0.1",
  "id": "20183910121.UPNWsDxwmn",
  "error": {
    "code": -32601,
    "message": "Object not found"
  }
}

Расширенное сообщение о результатах работы метода

Некоторые методы, помимо успешного или неуспешного ответа, должны передать клиенту кастомизированное сообщение или данные. Для таких ответов применяется общий тип ExtendedResult. Выглядят такие ответы следующим образом:

{
  "jsonrpc": "2.0",
  "id": "20183995523.MO4E9baKRr",
  "result": {
    "extendedResult": {
      "code": 1,
      "message": "Заявка #180047823 принята в работу",
      "data": []
	}
  }
}
{
  "jsonrpc": "2.0",
  "id": "20183995523.MO4E9baKRr",
  "result": {
    "extendedResult": {
      "code": 0,
      "message": "Зона .child не поддерживается для регистрации",
      "data": []
	}
  }
}

Здесь code 1 означает успешное выполнение, а 0 ошибку. А поле data (объект дополнительных данных) может оставаться пустым.

К каким данным дает доступ API

Функции, к которым клиент может получить доступ после авторизации, разделены на несколько категорий в соответствии с бизнес-логикой: виртуальный хостинг, VPS, домены, партнерская программа, финансовый сервис. Внутри каждого раздела тоже есть иерархическая система из объектов, которые объединяют несколько функций — отдельных методов.

Реальный кейс использования API

Основные пользователи API — веб-мастера, которые управляют сразу несколькими аккаунтами разных клиентов. Для них это очень удобная фича. 

Если бы не было API, работа выглядела бы так: заходишь в панель управления одного клиента, выгружаешь данные (например, о балансе, доменах и SSL с приближающейся датой окончания регистрации), анализируешь их в таблице. А если нужно посмотреть метрики другого клиента или сравнить их, выходишь из предыдущего аккаунта и заходишь в следующий. И так несколько раз.

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

В нашем API используются разные методы для управления аккаунтами, подробно мы их описывали в документации. Один из популярных — https://api.sweb.ru/pay/ метод index, он дает информацию о балансе аккаунта, количестве доменных бонусов, за счет которых можно бесплатно продлевать и регистрировать домены, статус аккаунта и ожидаемую дату блокировки.

Пример запроса на url: https://api.sweb.ru/pay

{
  "jsonrpc":"2.0"
   "id":"715670275654947.SpiAFUtJpx"
   "method":"index"
   "params":{}
}

Пример ответа:

{
  "jsonrpc":"2.0"
  "id":"715670275654947.SpiAFUtJpx"
  "params":[
        {
          "balance":{
          "real_balance":30
          "bonus_balance":0
         }
        "auto_payment_enable":0
        "isAutopaymentEnable":1
        "domainBonuses":0
        "status":"active"
         "blockInfo":{
         "days_date":"07.02.2023"
         "days":0
         "days_word":"дней"
}
"blockedMoney":0
"deferment":{
"show":false
"value":0
}
"edgeDate":"2022-11-14"
       }
   ]
}

Важная часть нашего API — возможность для клиентов управлять партнерской программой. Можно получать информацию о клиентах из партнерки, текущем балансе, настроить реферальные сайты. 

Для верифицированных клиентов доступны методы, с помощью которых можно создавать отдельные аккаунты под привлеченных клиентов — createOrderVh, checkLogin, createOrderVip и другие. 

Используя их, можно сделать форму регистрации, с помощью которой, не переходя на сайт SpaceWeb, можно зарегистрироваться на виртуальном хостинге или VPS. А созданный аккаунт будет автоматически подключен к партнерской программе. У конкурентов такого нет.

Планы по развитию API

В первую очередь будем расширять количество методов. Добавим управление почтой и DNS, методы для управления облачными платформами: балансировщиком, базой данных и бэкапами. Подготовим открытые коды для клиентов — их можно будет интегрировать в любые сервисы.

Еще хотим внедрить в API потоковые данные, например снэпшоты VPS. Они весят достаточно много, но мы хотим попробовать перейти на спецификацию, которая поддерживает передачу именно потоковых данных. 

А вы используете API при работе с хостинг-провайдерами? Каких методов вам не хватает?

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


  1. Rsa97
    21.03.2024 15:06

    Расширенное сообщение о результатах работы метода

    Непонятно, для чего это нужно. JSON-RPC допускает использование любых кодов ошибок, кроме блока от -32768 до -32000, закреплённого стандартом для своих целей. Так что вместо

    {
      "jsonrpc": "2.0",
      "id": "20183995523.MO4E9baKRr",
      "result": {
        "extendedResult": {
          "code": 0,
          "message": "Зона .child не поддерживается для регистрации",
          "data": []
    	}
      }
    }
    

    логичнее использовать

    {
      "jsonrpc": "2.0",
      "id": "20183995523.MO4E9baKRr",
      "error": {
        "code": 0,
        "message": "Зона .child не поддерживается для регистрации"
      }
    }


  1. evgensenin
    21.03.2024 15:06

    Лучше попросили программистов расширить список поддерживаемых доменов, хотели перенести сайт на домене .world - не получилось, не поддерживаете, техподдержка также закрыла вопрос таким же ответом. Приходится менять хостинг