Перейти к содержимому

Своё API для приложения - контроллер, токен, версии

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

Механика

Ядро умеет вызывать действия контроллеров по имени. Клиент обращается к общей точке входа и называет действие, платформа находит класс, проверяет фильтры, выполняет метод и превращает его результат в ответ. Разбирать запрос и собирать ответ вручную не нужно.

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

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

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

Отдельно живёт вопрос, кто отвечает за клиента. Мобильное приложение обновляется через магазины приложений и с задержкой в недели, а партнёрская интеграция может не обновляться годами. Любая правка API поэтому планируется как правка чужого кода: с предупреждением, переходным периодом и возможностью откатиться.

Шаги

  1. Завести класс контроллера в своём модуле и описать его действия.
  2. Зарегистрировать пространство имён своих контроллеров в файле настроек своего модуля.
  3. Отключить штатные фильтры сессии и включить свою проверку токена.
  4. Договориться о формате успешного ответа и об ошибках, описать его.
  5. Ограничить частоту вызовов и объём выдачи, чтобы API не заменил собой нагрузку сайта.
  6. Заложить версию в адрес до первой публикации приложения.

Код

Описываем контроллер и его действия:

/local/modules/vendor.shop/lib/Controller/Api.php
namespace Vendor\Shop\Controller;
use Bitrix\Main\Engine\Controller;
class Api extends Controller
{
public function configureActions()
{
return ['getCatalog' => ['prefilters' => []]]; // пустой список: фильтры свои
}
public function getCatalogAction(int $sectionId, int $limit = 20): array
{
return ['items' => $this->loadItems($sectionId, min($limit, 100))];
}
}

Действие - это метод с окончанием в имени, а не отдельный файл. Возвращённый массив платформа сама превратит в ответ, а исключение внутри метода - в ответ с ошибкой.

Регистрируем пространство имён:

/local/modules/vendor.shop/.settings.php
return [
'controllers' => ['value' => [
'defaultNamespace' => '\\Vendor\\Shop\\Controller',
], 'readonly' => true],
];
// после этого действие зовётся именем vendor:shop.Api.getCatalog

Имя действия собирается из модуля, класса и метода. Оно и есть адрес, по которому клиент обращается, и менять его после публикации приложения так же больно, как менять формат ответа.

Проверяем токен вместо сессии:

// свой фильтр: проверяет заголовок и авторизует под служебным пользователем
$token = $request->getHeader('Authorization');
$row = TokenTable::getList(['filter' => ['=TOKEN' => $token, '=ACTIVE' => 'Y']])->fetch();
if (!$row) {
$this->addError(new \Bitrix\Main\Error('Токен не принят', 'AUTH_FAILED'));
return null; // дальше действие не выполняется
}
$GLOBALS['USER']->Authorize($row['USER_ID'], false, false);
// второй и третий аргументы: не запоминать вход и не поднимать сессию браузера

Токен приносит внешний клиент, и проверять его нужно до всякой работы. Учётная запись под токеном - служебная, с правами ровно на то, что API отдаёт, а не администраторская «чтобы точно работало».

Договариваемся о формате ответа:

{ "status": "success", "data": { "items": [] }, "errors": [] }
{ "status": "error", "data": null,
"errors": [ { "code": "AUTH_FAILED", "message": "Токен не принят" } ] }

Формат одинаков для всех действий, включая ошибки. Клиент разбирает ответ одним куском кода, а не десятью, и добавление нового действия не требует правок на его стороне. Код ошибки при этом важнее её текста: текст читает человек, а решение принимает программа, и делает она это именно по коду.

Ограничиваем частоту и объём:

$key = 'api_rate_' . $row['ID'] . '_' . date('YmdH');
$count = (int)Option::get('vendor.shop', $key, 0);
if ($count > 1000) { // предел на час
$this->addError(new \Bitrix\Main\Error('Слишком часто', 'RATE_LIMIT'));
return null;
}
Option::set('vendor.shop', $key, $count + 1);

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

Пишем журнал обращений:

ApiLogTable::add([
'TOKEN_ID' => $row['ID'], 'ACTION' => $action,
'CODE' => $httpCode, 'TIME' => $ms, 'DATE_CREATE' => new \Bitrix\Main\Type\DateTime(),
]);
// журнал отвечает на вопросы «когда сломалось» и «кто именно так делает»
// время ответа пишут числом: по нему видно, когда чужой клиент стал тяжёлым

Журнал обращений окупается на первом же споре с разработчиком приложения. Без него разговор идёт в формате «у нас всё работает» против «у нас ничего не работает», и рассудить его нечем.

Ограничения

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

Общая точка входа отвечает на том же сайте и той же нагрузке, что и витрина. Тяжёлые выборки в API замедляют покупателей, поэтому объём выдачи ограничивают сверху, а не оставляют на усмотрение клиента.

Обращения из браузера с другого домена упираются в правила источника запроса. Их решают заголовками на уровне веб-сервера, и это отдельная договорённость с теми, кто занимается сервером.

Отдача больших списков через API упирается ещё и в память процесса. Ответ на пятьдесят тысяч позиций собирается в память целиком, поэтому выдачу делают страницами с обязательным пределом, а не по запросу клиента «отдайте всё».

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

Описание API стоит вести в том же репозитории, что и код. Отдельный документ в чужом хранилище устаревает за месяц, а описание рядом с контроллером правится тем же коммитом, что и само действие.

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

Типичные проблемы

Внешний клиент получает отказ на каждом запросе.

Действие ждёт сессию и признак сеанса, которых у клиента нет. Штатные фильтры действия заменяют своей проверкой токена в заголовке входящего запроса.

Приложение сломалось после правки на сайте.

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

API работает под правами администратора.

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

Сайт лёг из-за одного приложения.

Ограничения частоты вызовов нет, и клиент обращается к API в цикле. Предел вызовов ставят до публикации приложения, а не после первого падения сайта.

Отозвали один токен, перестали работать все.

Все клиенты приложения ходят под одним общим на всех выданным токеном. Токен выдают свой каждому приложению и каждому партнёру строго по отдельности.

Частые вопросы

Чем своё API лучше штатного REST?

Оно отдаёт ровно ваши данные в вашем формате. Штатный REST удобнее там, где хватает его методов.

Где хранить токены?

В своей таблице с признаком владельца и активности. Тогда отзыв одного не задевает остальных.

Как выпускать новую версию API?

Отдельным пространством имён или префиксом адреса. Старая версия живёт, пока живут приложения на ней.

Нужна ли документация, если клиент один?

Нужна: через год клиента будет вести другой человек. Описание действий и ошибок живёт рядом с кодом.

Смежное

Первоисточник