Как исправить ошибку «Неправильный формат сообщения» при чтении XML в мобильном приложении 1С?

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

Разработка двустороннего обмена данными между центральной базой (ЦБ) и мобильным приложением (МП) на платформе «1С:Предприятие 8» — задача, часто требующая ручной обработки XML-пакетов. Использование стандартных механизмов планов обмена отлично подходит для идентичных конфигураций. Однако, когда структуры метаданных в центральной базе и на мобильном устройстве различаются (например, на мобильном клиенте созданы только необходимые справочники с урезанным набором реквизитов), разработчикам приходится прибегать к ручному разбору XML-файла сообщения. В процессе реализации такого обмена часто возникает критическая ошибка: «Неправильный формат сообщения» при вызове метода ЧтениеСообщения.ЗакончитьЧтение().

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

Причина ошибки «Неправильный формат сообщения» при вызове ЗакончитьЧтение()

Для того чтобы понять, почему возникает данная ошибка, проанализируем, как платформа «1С:Предприятие» работает с XML-сообщениями планов обмена. Процесс чтения сообщения состоит из нескольких последовательных этапов:

  1. Создается объект чтения сообщения с помощью метода ПланыОбмена.СоздатьЧтениеСообщения().
  2. Инициализируется объект чтения XML-файла (ЧтениеXML), в который передается XML-данные (из файла или строки).
  3. Вызывается метод ЧтениеСообщения.НачатьЧтение(ЧтениеXML). На этом этапе платформа считывает технический «конверт» сообщения: заголовок, узел-отправитель, узел-получатель, а также номер принятого сообщения. Указатель (курсор) XML-чтения останавливается строго на начале секции тела сообщения (обычно это узел данных).
  4. Разработчик в цикле обходит элементы тела сообщения, считывая их с помощью методов ПрочитатьXML() или собственных функций разбора.
  5. После прочтения всех данных вызывается метод ЧтениеСообщения.ЗакончитьЧтение(), который проверяет целостность структуры XML-конверта и закрывает сессию чтения сообщения обмена.

Суть проблемы: Ошибка «Неправильный формат сообщения» возникает в момент вызова ЗакончитьЧтение(), если указатель (курсор) объекта ЧтениеXML сместился относительно ожидаемой платформой позиции. Платформа ожидает, что после завершения цикла чтения объектов XML-курсор будет указывать на закрывающий тег тела сообщения (например, </v8msg:Body> или аналогичный служебный тег конверта).

Если в процессе ручного разбора XML в пользовательских функциях (таких как ПрочитатьXMLСклад) курсор был сдвинут «лишним» вызовом метода ЧтениеXML.Прочитать() вперед, или наоборот — не дошел до конца элемента, то метод ЗакончитьЧтение() считывает неверный тег XML и генерирует исключение.

Разбор практического примера кода обмена

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

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


&НаСервере
Функция ОбменСЦентральнойБазойНаСервере()
    Попытка
        ВСОпределение = Новый WSОпределения("http://server/base/ws/ExchangeTP?wsdl");
        ВебСервис = ВСОпределение.Сервисы.Получить("ExchangeTP", "ExchangeTP");
        ТочкаПодключения = ВебСервис.ТочкиПодключения.Получить("ExchangeTPSoap");
        ВебОперация = ТочкаПодключения.Интерфейс.Операции.Получить("НачатьОбмен");
        
        // Формируем пакет исходящих данных из МП в ЦБ
        Данные = Новый ХранилищеЗначения(СформироватьСообщениеДляЦентральнойБазы(), Новый СжатиеДанных(9));
        ДанныеXDTO = ВСОпределение.ФабрикаXDTO.Создать(ВебОперация.Параметры.Получить("Данные").Тип, Данные);
        
        ВебПрокси = Новый WSПрокси(ВСОпределение, "ExchangeTP", "ExchangeTP", "ExchangeTPSoap");
        Ответ = ВебПрокси.НачатьОбмен(ДанныеXDTO);
        
        // Обрабатываем полученный ответ (XML-строку) от ЦБ
        ПринятьСообщениеИзЦентральнойБазы(Ответ.Получить());
        Возврат Истина;
    Исключение
        Сообщить(ОписаниеОшибки());
        Возврат Ложь;
    КонецПопытки;
КонецФункции

Теперь детально рассмотрим процедуру приема и обработки входящего XML-сообщения ПринятьСообщениеИзЦентральнойБазы:


&НаСервере
Процедура ПринятьСообщениеИзЦентральнойБазы(СтрокаСообщения)
    ЧтениеXML = Новый ЧтениеXML;
    ЧтениеXML.УстановитьСтроку(СтрокаСообщения);
    
    ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения;
    ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
    
    // Удаляем регистрацию изменений для подтверждения успешного приема
    ПланыОбмена.УдалитьРегистрациюИзменений(ЧтениеСообщения.Отправитель, ЧтениеСообщения.НомерПринятого);
    
    // Обход тела сообщения и чтение объектов данных
    Пока ВозможностьЧтенияXML(ЧтениеXML) Цикл
        Данные = ПрочитатьДанные(ЧтениеXML);
        
        Если Данные <> Неопределено Тогда
            // Бизнес-логика фильтрации и обработки данных
            Если ТипЗнч(Данные) = Тип("ДокументОбъект.ОбслуживаниеКлиентовТП") Тогда
                Если Данные.ТорговыйПредставитель <> ПланыОбмена.МобильныеУстройства.ЭтотУзел.ТорговыйПредставитель Тогда
                    Продолжить;
                КонецЕсли;
            КонецЕсли;
            
            Данные.ОбменДанными.Отправитель = ЧтениеСообщения.Отправитель;
            Данные.ОбменДанными.Загрузка = Истина;
            Данные.Записать();
        КонецЕсли;
    КонецЦикла;
    
    // Критическая точка: закрытие сессии чтения сообщения
    ЧтениеСообщения.ЗакончитьЧтение();
КонецПроцедуры

Реализация ручной десериализации (чтения) разнородных объектов

Когда конфигурации центральной базы и мобильного приложения не идентичны, стандартный метод ПрочитатьXML(ЧтениеXML) не сможет сопоставить типы и вернет Неопределено или вызовет ошибку метаданных. Поэтому мы должны переопределить логику чтения в зависимости от XML-типа элемента. Рассмотрим функцию ПрочитатьДанные:


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

А теперь детально проанализируем функцию пошагового разбора XML-данных конкретного справочника «Склады» — ПрочитатьXMLСклад. Обратите особое внимание на позиционирование курсора XML:


Функция ПрочитатьXMLСклад(ЧтениеXML)
    // Проверяем, что парсер находится на начале элемента (открывающий тег склада)
    Если ЧтениеXML.ТипУзла <> ТипУзлаXML.НачалоЭлемента Тогда
        ВызватьИсключение "Ошибка чтения XML: Ожидалось начало элемента Склад";
    КонецЕсли;
    
    // Переходим к следующему узлу внутри элемента Склад
    ЧтениеXML.Прочитать(); 
    
    // Читаем Уникальный Идентификатор (ссылку) склада
    СкладСсылка = ПрочитатьXML(ЧтениеXML);
    Если ТипЗнч(СкладСсылка) <> Тип("СправочникСсылка.Склады") Тогда
        ВызватьИсключение "Ошибка чтения XML: Неверный тип ссылки склада";
    КонецЕсли;
    
    // Инициализируем объект в базе данных мобильного приложения
    СкладОбъект = СкладСсылка.ПолучитьОбъект();
    Если СкладОбъект = Неопределено Тогда
        СкладОбъект = Справочники.Склады.СоздатьЭлемент();
        СкладОбъект.УстановитьСсылкуНового(СкладСсылка);
    КонецЕсли;
    
    // Последовательно считываем реквизиты объекта
    СкладОбъект.Наименование = ПрочитатьXML(ЧтениеXML);
    СкладОбъект.Код = ПрочитатьXML(ЧтениеXML);
    
    // Проверяем, что после чтения всех реквизитов мы находимся на закрывающем теге
    Если ЧтениеXML.ТипУзла <> ТипУзлаXML.КонецЭлемента Тогда
        ВызватьИсключение "Ошибка чтения XML: Ожидался конец элемента Склад";
    КонецЕсли;
    
    // ВАЖНО: Делаем финальное чтение, чтобы сдвинуть курсор за пределы закрывающего тега
    // и подготовить XML-парсер к чтению следующего объекта в цикле!
    ЧтениеXML.Прочитать();
    
    Возврат СкладОбъект;
КонецФункции

Анализ сопутствующей ошибки: Блокировки при записи (ошибка Client...)

В процессе отладки обменов разработчики часто сталкиваются с ошибкой вида: «Операция не может быть выполнена в процессе записи. Код ошибки: Client...» на строке отправки запроса ВебПрокси.НачатьОбмен(ДанныеXDTO).

Давайте разберем физику этого процесса. Эта ошибка возникает по двум основным причинам:

Рекомендации по проектированию надежного обмена

Для создания стабильного и масштабируемого обмена данными между разнородными конфигурациями рекомендуем придерживаться следующих правил:

  1. Используйте XDTO-пакеты вместо ручного XML-парсинга: Описание структуры обмена в схемах XDTO избавляет от необходимости вручную вызывать методы ЧтениеXML.Прочитать() и контролировать положение курсора. Фабрика XDTO автоматически валидирует и сопоставляет входящие XML-структуры, заполняя свойства объектов.
  2. Переходите на формат JSON: Для мобильных приложений формат JSON является более легковесным, быстрее парсится и потребляет значительно меньше интернет-трафика по сравнению с избыточным XML-форматом.
  3. Тщательно обрабатывайте исключения: Всегда оборачивайте блоки десериализации данных в конструкции Попытка...Исключение...КонецПопытки. Это позволит продолжить чтение файла обмена, даже если один из объектов пакета поврежден или содержит несовместимые данные.
← На главную