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

Не работает AJAX-запрос - разбор причин

Запрос уходит, а на странице ничего не меняется: ответа нет, он пустой или не разбирается. Разбираем причины по убыванию частоты.

С чего начать

Смотрим сырой ответ, а не то, что показал скрипт:

Окно терминала
curl -s -X POST 'https://example.com/bitrix/services/main/ajax.php?action=local:feedback.feedback.save' \
-d 'name=test' | head -c 400
# в ответе должен быть только JSON вида {"status":...,"data":...,"errors":[...]}
# любая строка до открывающей скобки ломает разбор ответа на стороне браузера

Сырой ответ отвечает сразу на два вопроса: дошёл ли запрос и что вернул сервер. Предупреждение PHP или кусок разметки перед данными объясняют почти половину жалоб «ответ пришёл, но скрипт его не понял».

Читаем описание ошибки на стороне браузера:

BX.ajax.runAction('local:feedback.feedback.save', { data: { name: 'test' } })
.then((r) => console.log('ответ', r.data))
.catch((r) => console.log('ошибки', r.errors)); // здесь код и текст отказа

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

Проверяем подпись запроса:

// легаси-файл, а не контроллер: проверки пишут руками
if (!check_bitrix_sessid()) {
http_response_code(403); // ключ не передан или уже не тот
exit;
}
// в форму ключ ставят вызовом bitrix_sessid_post(), в запрос - BX.bitrix_sessid()

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

Ищем лишний вывод в своём коде:

Окно терминала
grep -rn 'var_dump\|print_r\|echo ' /home/bitrix/www/local/php_interface/ | head
tail -20 /var/log/php-fpm/bx0-error.log | grep -i 'warning\|notice'
# предупреждение PHP попадает в тело ответа наравне с данными
# разбор JSON после такой строки падает уже на стороне браузера

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

Причины

  1. В ответе есть лишний вывод примерно 30% случаев

    ПризнакОтвет приходит, но скрипт сообщает о неразобранном ответе или молча ничего не делает.

    ПроверкаСмотрим сырой ответ запросом из консоли и читаем его первые символы.

    Что делатьУбираем отладочную печать и предупреждения PHP, а в подключаемых файлах - пробелы перед тегом.

  2. Действие не найдено примерно 25% случаев

    ПризнакВ ответе описание ошибки о ненайденном действии или обычная страница вместо данных.

    ПроверкаСверяем идентификатор действия с пространством имён, классом и методом контроллера.

    Что делатьПравим идентификатор и проверяем автозагрузку класса: без неё контроллер не находится.

  3. Запрос отклонён проверкой подписи примерно 20% случаев

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

    ПроверкаСмотрим, передаётся ли ключ сессии в запросе, и сравниваем его с текущим значением.

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

  4. Действию не хватает прав примерно 15% случаев

    ПризнакУ авторизованного всё работает, а гость получает отказ или страницу входа вместо данных.

    ПроверкаПроверяем правила доступа действия и права группы на нужный раздел данных.

    Что делатьРазрешаем действие гостю осознанно либо возвращаем понятную ошибку вместо молчания.

  5. Запрос уходит не на тот адрес примерно 10% случаев

    ПризнакВ консоли браузера видна ошибка смешанного содержимого или запрет обращения к чужому домену.

    ПроверкаСмотрим полный адрес запроса во вкладке сети: протокол, домен и путь.

    Что делатьСтроим адрес средствами платформы, а не собираем строкой: домен и протокол подставятся сами.

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

Почему в ответе приходит целая страница?

Запрос попал не на действие контроллера, а на обычную страницу сайта: неверный адрес или ошибка в идентификаторе действия. Сырой ответ показывает это сразу.

Как передать ключ сессии в запросе?

Штатными средствами: в форме - служебным вызовом, в запросе из скрипта - значением ключа текущей сессии. Проверка сверяет параметр запроса или заголовок с этим значением.

Можно ли отключить проверку подписи?

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

Почему запрос работает у администратора?

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

Где смотреть ошибку, если в консоли пусто?

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

Смежное

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