Часто разработчики сталкиваются с ситуацией, когда код, идеально работающий при ручном запуске из сеанса пользователя, начинает сбоить при автоматическом выполнении в регламентном задании на сервере. Ошибки могут быть самыми разными, и их диагностика усложняется тем, что фоновый процесс выполняется в ином контексте, чем клиентский сеанс. Рассмотрим классический пример такой проблемы: HTTP-запрос к внешнему API работает вручную, но падает в регламентном задании с ошибкой Object field not found (data).
Проанализируем ситуацию и по шагам найдем причину и решение этой распространенной проблемы.
Автор столкнулся с тем, что процедура получения заказов из API маркетплейса Kaspi, размещенная в общем модуле, при ручном запуске регламентного задания отрабатывает успешно. Но когда то же самое задание запускается автоматически по расписанию, оно завершается с ошибкой в строке перебора полученных данных:
Object field not found (data)
{ОбщийМодуль.KaspiAPIЗапуск.Модуль(48)}: Для Каждого Значение Из ОбъектJSON.data Цикл
На первый взгляд, кажется, что сервер API просто не вернул массив data. Однако автор утверждает, что даже при отсутствии новых заказов этот объект должен присутствовать в ответе, пусть и пустой. Ключевая зацепка здесь — разница в поведении при ручном и автоматическом запуске. Это почти всегда указывает на различия в окружении или контексте выполнения.
Первая и самая важная ошибка в отладке кода, работающего с API, — это отсутствие проверки кода состояния HTTP-ответа. Полагаться на то, что ответ всегда будет успешным (код 200) и содержать ожидаемую структуру JSON, — плохая практика. В первую очередь необходимо модифицировать код, чтобы логировать реальный ответ сервера в случае ошибки.
Добавим простую проверку и запись в журнал регистрации, если код ответа отличается от 200.
Результат = HTTP.Получить(Запрос);
// Добавляем проверку и логирование
Если Результат.КодСостояния = 200 Тогда
ОтветJSON = Результат.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ОтветJSON);
ОбъектJSON = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// ... дальнейшая обработка ...
Иначе
// Если код состояния не 200, логируем ошибку
ТекстОшибки = "Ошибка HTTP-запроса. Код: " + Результат.КодСостояния +
". Тело ответа: " + Результат.ПолучитьТелоКакСтроку();
ЗаписьЖурналаРегистрации("Интеграция.Kaspi", УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
// Прерываем выполнение, чтобы не было ошибки на разборе JSON
Возврат;
КонецЕсли;
// Эта строка теперь будет выполняться только при успешном ответе
Для Каждого Значение Из ОбъектJSON.data Цикл
// ...
КонецЦикла;
После этой доработки и очередного автоматического запуска задания в журнале регистрации обнаруживается истинная причина: сервер возвращает не 200, а 500 Internal Server Error, а в теле ответа содержится сообщение: {"errors":[{"title":"Some error occurred when calling the api"}]}.
Это полностью меняет картину. Ошибка не в том, что в JSON нет поля data, а в том, что наш запрос, отправленный с сервера 1С, является некорректным с точки зрения API, и он даже не пытается сформировать успешный ответ.
Итак, мы выяснили, что при автоматическом запуске на сервере формируется "битый" HTTP-запрос. Почему же при ручном запуске он корректен? Давайте внимательно посмотрим на код формирования URL:
ТекДата = ТекущаяДата();
ИсхДата = Дата(1970, 1, 1);
ДатаВМил = (ТекДата - ИсхДата) * 1000;
ДатаВМилСтрока = СтрЗаменить(Строка(ДатаВМил), " ", ""); // <--- Проблемная строка
ДатаВмилНачало = ДатаВМил - 600 * 1000 * 100;
ДатаВМилНачалоСтрока = СтрЗаменить(Строка(ДатаВмилНачало), " ", ""); // <--- И еще одна
// ...
Запрос = Новый HTTPЗапрос("/shop/api/v2/orders? ... &filter[orders][creationDate][$ge]=" + ДатаВМилНачалоСтрока + "&filter[orders][creationDate][$le]=" + ДатаВМилСтрока + "...");
Здесь кроется корень зла. Функция Строка(), примененная к числу, преобразует его в строковое представление, используя региональные настройки текущего сеанса. Эти настройки определяют, какой символ использовать в качестве разделителя групп разрядов (тысяч, миллионов и т.д.).
СтрЗаменить(..., " ", "").Ваш вызов СтрЗаменить(..., " ", "") ищет и заменяет только обычный пробел (код 32), а неразрывный пробел остается в строке. В итоге, вместо `1677024000000` в URL запроса подставляется что-то вроде `1 677 024 000 000`, где пробелы — неразрывные. Сервер API не может распознать такое "число" и возвращает ошибку 500.
Никогда не используйте функцию Строка() для преобразования чисел, которые будут использоваться в API, файловых обменах или других программных интерфейсах. Для этих целей существует функция Формат(), которая позволяет точно управлять представлением данных.
Чтобы получить строковое представление числа без разделителей групп разрядов, нужно использовать специальный параметр форматной строки "ЧГ=0" (что означает "Число Групп = 0").
Исправим проблемные строки в коде:
// Было
// ДатаВМилСтрока = СтрЗаменить(Строка(ДатаВМил), " ", "");
// ДатаВМилНачалоСтрока = СтрЗаменить(Строка(ДатаВмилНачало), " ", "");
// Стало: ПРАВИЛЬНЫЙ ВАРИАНТ
ДатаВМилСтрока = Формат(ДатаВМил, "ЧГ=0");
ДатаВМилНачалоСтрока = Формат(ДатаВмилНачало, "ЧГ=0");
После такого исправления код будет работать одинаково корректно и на клиенте, и на сервере, вне зависимости от региональных настроек. Сформированный URL всегда будет содержать "чистые" числа, и API будет обрабатывать запросы успешно.
Если по какой-то причине вы не можете использовать Формат(), можно исправить исходный код, заменяя именно неразрывный пробел. Для этого в 1С есть системное перечисление Символы.
// Замена неразрывного пробела
СтрокаСПробелами = Строка(ДатаВМил);
СтрокаБезПробелов = СтрЗаменить(СтрокаСПробелами, Символы.НПП, "");
Однако этот способ менее надежен, так как он лечит симптомы, а не причину. Использование Формат() является идиоматически верным решением для задач сериализации данных.
Строка() для данных. Для чисел, дат и других типов, предназначенных для обмена с другими системами, всегда используйте функцию Формат() с явным указанием формата.Применение этих простых правил поможет избежать многочасовой отладки и сделает ваш код более надежным и предсказуемым.