Как реализовать повторный HTTP-запрос в 1С при получении некорректного XML-файла

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе интеграции 1С со сторонними сервисами программисты часто сталкиваются с нестабильной работой внешних API. Типичная ситуация: сервер принимает запрос, но возвращает некорректный или неполный ответ. В этой статье мы подробно разберем, как построить надежный алгоритм повторных запросов, научимся проверять содержимое XML «на лету» и устраним популярные ошибки, такие как «Ошибка совместного доступа к файлу».

Постановка задачи и анализ ситуации

Представим сценарий: ваша обработка отправляет запрос на сервер для получения данных (например, телеметрии или списка заказов). Сервер из-за внутренних ошибок иногда присылает XML, в котором отсутствует часть данных. При этом HTTP-код состояния может быть успешным (200 OK), но бизнес-логика не соблюдена. Например, в теге messages атрибут count (количество записей) равен 1 вместо положенных 6 или более. Единственный выход в такой ситуации — повторять запрос до тех пор, пока не придет валидный файл.

Вариант 1: Простой цикл с ограничением попыток

Рассмотрим самый простой способ реализации механизма повторов. Нам необходимо организовать цикл, который будет выполняться до тех пор, пока условие успешности не будет выполнено, либо пока не будет достигнут лимит попыток. Ограничение попыток критически важно, чтобы не войти в бесконечный цикл и не «повесить» сеанс пользователя.

Проанализируем пример кода:


ЧислоПопытокЗапроса = 5;
ТекущаяПопытка = 0;
Успех = Ложь;

Пока ТекущаяПопытка < ЧислоПопытокЗапроса И НЕ Успех Цикл
    ТекущаяПопытка = ТекущаяПопытка + 1;
    
    // Выполняем HTTP-запрос
    Ответ = Соединение.ОтправитьДляОбработки(Запрос);
    Если Ответ.КодСостояния = 200 Тогда
        // Сохраняем ответ и проверяем данные
        ТекстОтвета = Ответ.ПолучитьТелоКакСтроку();
        Если ДанныеКорректны(ТекстОтвета) Тогда
            Успех = Истина;
        Иначе
            // Если данные плохие, можно добавить паузу перед следующим шагом
            ОбщийМодуль.Пауза(2); 
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Вариант 2: Извлечение условий из XML-структуры

Чтобы понять, нужно ли повторять запрос, нам необходимо «заглянуть» внутрь XML. Рассмотрим ситуацию, когда признаком ошибки является значение атрибута. Для этого используем объект ЧтениеXML. Он работает быстро и позволяет читать файл последовательно, не загружая всё дерево объектов в память.

Разберем по шагам, как извлечь атрибут count из узла messages:


ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТекстОтвета);

Пока ЧтениеXML.Прочитать() Цикл
    // Проверяем, что мы находимся в начале нужного элемента
    Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента И ЧтениеXML.Имя = "messages" Тогда
        // Пытаемся прочитать атрибут count
        Если ЧтениеXML.ПрочитатьАтрибут() И ЧтениеXML.Имя = "count" Тогда
            КоличествоЗаписей = Число(ЧтениеXML.Значение);
            Если КоличествоЗаписей < 6 Тогда
                // Условие не выполнено, нужно повторить запрос
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
КонецЦикла;
ЧтениеXML.Закрыть();

Проблема совместного доступа и работа с файлами

Часто разработчики записывают тело ответа во временный файл на диске через Ответ.ПолучитьТелоКакДвоичныеДанные().Записать(ПутьКФайлу). Если при повторном запросе вы попытаетесь перезаписать этот же файл, не закрыв предыдущий объект ЧтениеXML, система выдаст ошибку: «Процесс не может получить доступ к файлу, так как этот файл занят другим процессом».

Выясним причину: объект ЧтениеXML блокирует файл до тех пор, пока не будет вызван метод Закрыть() или пока объект не будет удален сборщиком мусора.

Рекомендация: Чтобы избежать подобных проблем, лучше вообще отказаться от записи на диск и работать с памятью, используя метод УстановитьСтроку() или потоки данных. Если работа с файлом неизбежна, структура кода должна быть следующей:


ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(АдресФайла);
// ... логика разбора ...
ЧтениеXML.Закрыть(); // Обязательно закрываем перед новой итерацией!

// Теперь файл можно безопасно перезаписать
Ответ.ПолучитьТелоКакДвоичныеДанные().Записать(АдресФайла);

Вариант 3: Рекурсивный подход

Для более элегантного кода можно использовать рекурсию. Создадим функцию, которая вызывает сама себя в случае неудачи, передавая счетчик попыток. Это позволяет разделить логику запроса и логику обработки данных.

Рассмотрим пример реализации рекурсивной функции:


Функция ПолучитьВалидныеДанные(ПараметрыЗапроса, Попытка = 1) Экспорт
    МаксПопыток = 10;
    
    Если Попытка > МаксПопыток Тогда
        ЗаписатьВЖурналРегистрации("Не удалось получить данные после 10 попыток", УровеньЖурналаРегистрации.Ошибка);
        Возврат Неопределено;
    КонецЕсли;

    Соединение = Новый HTTPСоединение(ПараметрыЗапроса.Сервер);
    Запрос = Новый HTTPЗапрос(ПараметрыЗапроса.Ресурс);
    Ответ = Соединение.Получить(Запрос);
    
    ТекстXML = Ответ.ПолучитьТелоКакСтроку();
    
    // Быстрая проверка через ЧтениеXML
    Если ПроверитьВалидность(ТекстXML) Тогда
        Возврат ТекстXML;
    Иначе
        // Рекурсивный вызов с увеличением счетчика
        Возврат ПолучитьВалидныеДанные(ПараметрыЗапроса, Попытка + 1);
    КонецЕсли;
КонецФункции

Оптимизация и промышленный подход

При реализации механизмов повторов в реальных проектах стоит учитывать дополнительные аспекты, которые сделают вашу систему отказоустойчивой:

1. Экспоненциальная задержка (Exponential Backoff)

Если сервер перегружен, частые повторные запросы могут сделать ситуацию только хуже (эффект «шторма запросов»). Проанализируем методику увеличения паузы: первую попытку делаем через 1 секунду, вторую через 2, третью через 4 и так далее. Это дает внешнему ресурсу время на восстановление.

2. Использование XDTO для глубокой проверки

Если простая проверка атрибута count недостаточна, можно задействовать ФабрикуXDTO. Это позволит проверить XML на соответствие схеме (XSD). Однако помните, что чтение через XDTO требует больше ресурсов, чем ЧтениеXML, поэтому используйте его только тогда, когда уверены в структуре файла.

3. Логирование «битых» файлов

Чтобы иметь доказательства некорректной работы стороннего сервиса для общения с техподдержкой контрагента, обязательно сохраняйте ошибочные XML-ответы в лог или отдельную папку. Это поможет выяснить причину «рандомных» сбоев.

Проанализируем пример комплексного разбора с использованием Попытка...Исключение:


Попытка
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.УстановитьСтроку(ТекстОтвета);
    // Используем ФабрикуXDTO для разбора, если структура сложная
    ДанныеXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML, ТипXDTO);
    ЧтениеXML.Закрыть();
Исключение
    // Сюда попадем, если XML поврежден синтаксически
    ЗаписатьВЖурналРегистрации("Ошибка парсинга XML: " + ОписаниеОшибки());
    // Здесь также можно инициировать повторный запрос
КонецПопытки;

Заключение

Мы рассмотрели несколько способов реализации повторных запросов в 1С. Основной вывод: всегда ограничивайте количество итераций, не забывайте освобождать ресурсы (закрывать файлы и объекты чтения) и старайтесь использовать работу со строками в оперативной памяти вместо временных файлов. Это сделает ваш код быстрее и избавит от ошибок блокировки в многопользовательской среде.

← На главную