Почему регламентное задание с HTTP-запросом выдает ошибку, а ручной запуск работает корректно?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Часто разработчики сталкиваются с ситуацией, когда код, идеально работающий при ручном запуске из сеанса пользователя, начинает сбоить при автоматическом выполнении в регламентном задании на сервере. Ошибки могут быть самыми разными, и их диагностика усложняется тем, что фоновый процесс выполняется в ином контексте, чем клиентский сеанс. Рассмотрим классический пример такой проблемы: HTTP-запрос к внешнему API работает вручную, но падает в регламентном задании с ошибкой Object field not found (data).

Проанализируем ситуацию и по шагам найдем причину и решение этой распространенной проблемы.

Шаг 1. Анализ первоначального кода и симптомов

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


Object field not found (data)
{ОбщийМодуль.KaspiAPIЗапуск.Модуль(48)}:    Для Каждого Значение Из ОбъектJSON.data Цикл

На первый взгляд, кажется, что сервер API просто не вернул массив data. Однако автор утверждает, что даже при отсутствии новых заказов этот объект должен присутствовать в ответе, пусть и пустой. Ключевая зацепка здесь — разница в поведении при ручном и автоматическом запуске. Это почти всегда указывает на различия в окружении или контексте выполнения.

Шаг 2. Диагностика: проверяем реальный ответ сервера

Первая и самая важная ошибка в отладке кода, работающего с 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, и он даже не пытается сформировать успешный ответ.

Шаг 3. Поиск источника некорректного запроса: клиент vs. сервер

Итак, мы выяснили, что при автоматическом запуске на сервере формируется "битый" HTTP-запрос. Почему же при ручном запуске он корректен? Давайте внимательно посмотрим на код формирования URL:


ТекДата = ТекущаяДата();
ИсхДата = Дата(1970, 1, 1);
ДатаВМил = (ТекДата - ИсхДата) * 1000;
ДатаВМилСтрока = СтрЗаменить(Строка(ДатаВМил), " ", ""); // <--- Проблемная строка

ДатаВмилНачало = ДатаВМил - 600 * 1000 * 100;
ДатаВМилНачалоСтрока = СтрЗаменить(Строка(ДатаВмилНачало), " ", ""); // <--- И еще одна

// ...

Запрос = Новый HTTPЗапрос("/shop/api/v2/orders? ... &filter[orders][creationDate][$ge]=" + ДатаВМилНачалоСтрока + "&filter[orders][creationDate][$le]=" + ДатаВМилСтрока + "...");

Здесь кроется корень зла. Функция Строка(), примененная к числу, преобразует его в строковое представление, используя региональные настройки текущего сеанса. Эти настройки определяют, какой символ использовать в качестве разделителя групп разрядов (тысяч, миллионов и т.д.).

  1. Когда вы запускаете код вручную, используются ваши пользовательские настройки Windows/1С. Возможно, в них разделитель отключен или является обычным пробелом, который успешно удаляется функцией СтрЗаменить(..., " ", "").
  2. Когда код выполняется на сервере в фоновом задании (под системным пользователем), используются системные или стандартные региональные настройки. В русскоязычных локалях в качестве разделителя групп разрядов часто используется неразрывный пробел (символ с кодом 160).

Ваш вызов СтрЗаменить(..., " ", "") ищет и заменяет только обычный пробел (код 32), а неразрывный пробел остается в строке. В итоге, вместо `1677024000000` в URL запроса подставляется что-то вроде `1 677 024 000 000`, где пробелы — неразрывные. Сервер API не может распознать такое "число" и возвращает ошибку 500.

Шаг 4. Правильное решение: используем функцию `Формат()`

Никогда не используйте функцию Строка() для преобразования чисел, которые будут использоваться в API, файловых обменах или других программных интерфейсах. Для этих целей существует функция Формат(), которая позволяет точно управлять представлением данных.

Чтобы получить строковое представление числа без разделителей групп разрядов, нужно использовать специальный параметр форматной строки "ЧГ=0" (что означает "Число Групп = 0").

Исправим проблемные строки в коде:


// Было
// ДатаВМилСтрока = СтрЗаменить(Строка(ДатаВМил), " ", "");
// ДатаВМилНачалоСтрока = СтрЗаменить(Строка(ДатаВмилНачало), " ", "");

// Стало: ПРАВИЛЬНЫЙ ВАРИАНТ
ДатаВМилСтрока = Формат(ДатаВМил, "ЧГ=0");
ДатаВМилНачалоСтрока = Формат(ДатаВмилНачало, "ЧГ=0");

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

Альтернативный, но менее предпочтительный способ

Если по какой-то причине вы не можете использовать Формат(), можно исправить исходный код, заменяя именно неразрывный пробел. Для этого в 1С есть системное перечисление Символы.


// Замена неразрывного пробела
СтрокаСПробелами = Строка(ДатаВМил);
СтрокаБезПробелов = СтрЗаменить(СтрокаСПробелами, Символы.НПП, "");

Однако этот способ менее надежен, так как он лечит симптомы, а не причину. Использование Формат() является идиоматически верным решением для задач сериализации данных.

Итоги и рекомендации

  1. Всегда проверяйте код ответа HTTP. Это первый и главный шаг в отладке сетевых взаимодействий. Логируйте тело ответа при ошибках.
  2. Не используйте Строка() для данных. Для чисел, дат и других типов, предназначенных для обмена с другими системами, всегда используйте функцию Формат() с явным указанием формата.
  3. Помните о разнице контекстов. Код на сервере 1С выполняется в ином окружении (другой пользователь ОС, другие региональные настройки, отсутствие доступа к файлам пользователя и т.д.), чем код на клиенте.
  4. Отлаживайте фоновые задания. Платформа 1С позволяет подключать отладчик к фоновым заданиям. Для этого нужно разрешить отладку на сервере 1С и настроить параметры автоматического подключения в конфигураторе.

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

← На главную