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

Действия своего модуля по REST - включение и доступ

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

Решение

Чем отдать данные наружу

Сравниваем три штатных механизма отдачи данных:

SOAP-веб-сервис - легаси: класс IWebService, WSDL по ?wsdl, авторизация HTTP Basic
REST-интеграция - действия контроллеров модуля открываются по REST, нужен rest 18.5.1+
Модуль «REST API» - инфраструктура для локальных приложений и решений Маркетплейс

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

Включаем витрину модуля

Ставим признак REST-интеграции в настройках модуля:

/local/modules/vendor.shop/.settings.php
return [
'controllers' => [
'value' => [
'defaultNamespace' => '\\Vendor\\Shop\\Controller',
'restIntegration' => ['enabled' => true], // нужен модуль rest 18.5.1 и выше
],
'readonly' => true,
],
];

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

Закрываем лишние действия

Запрещаем вызов снаружи для всего контроллера:

use Bitrix\Main\Engine;
protected function getDefaultPreFilters()
{
return [
...parent::getDefaultPreFilters(), // распаковываем CSRF, метод запроса, аутентификацию
new Engine\ActionFilter\Scope(Engine\ActionFilter\Scope::NOT_REST),
];
}

Распаковка родительского списка обязательна: пересобранный с нуля набор снимает штатную защиту действия. Фильтр Scope с признаком NOT_REST отклоняет вызов, пришедший по REST, и не трогает обычные обращения со своих страниц.

Закрываем одно действие, оставляя соседние открытыми:

public function configureActions()
{
return ['clearCache' => ['prefilters' => [
new Engine\ActionFilter\Scope(Engine\ActionFilter\Scope::NOT_REST),
]]];
}

Список в configureActions заменяет префильтры названного действия целиком. Остальные действия контроллера остаются на общем наборе и по-прежнему видны внешнему клиенту.

Достаём REST-контекст

Принимаем контекст вызова необязательным параметром:

public function getCatalogAction(int $sectionId, \CRestServer $restServer = null): array
{
$clientId = $restServer ? $restServer->getClientId() : null; // кто именно зовёт
return ['items' => $this->loadItems($sectionId, $clientId)];
}

Необязательным параметр делаем ради совместимости: то же действие зовут из браузера обычным асинхронным запросом, и объекта CRestServer в нём нет. Обязательный параметр сломает этот путь ещё на разборе аргументов, до первой строки нашего кода.

Проверяем вызов и разбираем ответ

Зовём метод ключом и смотрим тело ответа:

Окно терминала
curl -s "https://example.com/rest/1/КЛЮЧ/$METHOD.json" | head -20
# успех: {"result": ..., "time": {"start": ..., "duration": ..., "processing": ...}}
# отказ: {"error": "NO_AUTH_FOUND", "error_description": "..."}

Успешный ответ приходит в поле result, рядом лежит служебное time с временами обработки. Отказ выглядит иначе: только error и error_description, поля result в нём нет вовсе.

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

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

Наружу открылось больше, чем планировали.

Признак restIntegration.enabled открывает по REST все действия всех контроллеров модуля. Каждое служебное действие закрывают префильтром отдельно, иначе наружу уходит и оно.

Обращение к /rest/ отвечает NO_AUTH_FOUND.

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

В ответе приходит INTERNAL_WRONG_HANDLER_CLASS.

Класс обработчика не найден по описанию метода: неверное пространство имён в настройках модуля или опечатка в имени класса.

После правки фильтров действие перестало работать из браузера.

Метод getDefaultPreFilters переопределён своим списком, без распаковки родительского набора. Вместе с ним ушли штатные проверки CSRF, метода запроса и аутентификации.

Асинхронный вызов падает на разборе аргументов.

У параметра \CRestServer не проставлено значение по умолчанию, поэтому он считается обязательным. При обращении из браузера значения для него нет, и действие не выполняется.

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

Как зарегистрировать свой метод REST?

Отдельной регистрации метода не требуется. Достаточно поставить признак restIntegration.enabled в настройках модуля: действия его контроллеров становятся доступны по REST. Нужен модуль rest версии 18.5.1 и выше.

Что за поле time в ответе REST-метода?

Служебные времена обработки запроса: начало, окончание и длительность. Оно приходит рядом с result при успешном вызове. В ответе с ошибкой ни result, ни time нет.

Можно ли закрыть одно действие, не выключая REST целиком?

Да, префильтром Scope с признаком NOT_REST у нужного действия. Список префильтров действия задают через configureActions, а общий для контроллера - через getDefaultPreFilters.

Чем это отличается от своего API на контроллере?

Здесь работает штатная витрина REST со своей авторизацией и общим форматом ответа. Своё API - это отдельный протокол поверх точки входа, где формат ответа, токены и версии придумываем сами.

Как понять, что действие действительно закрыто?

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

Смежное

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