Разработка двустороннего обмена данными между центральной базой (ЦБ) и мобильным приложением (МП) на платформе «1С:Предприятие 8» — задача, часто требующая ручной обработки XML-пакетов. Использование стандартных механизмов планов обмена отлично подходит для идентичных конфигураций. Однако, когда структуры метаданных в центральной базе и на мобильном устройстве различаются (например, на мобильном клиенте созданы только необходимые справочники с урезанным набором реквизитов), разработчикам приходится прибегать к ручному разбору XML-файла сообщения. В процессе реализации такого обмена часто возникает критическая ошибка: «Неправильный формат сообщения» при вызове метода ЧтениеСообщения.ЗакончитьЧтение().
В этой статье мы подробно разберем причины возникновения этой ошибки, детально проанализируем логику работы XML-парсера платформы «1С:Предприятие», рассмотрим примеры реализации процедур чтения и записи XML-данных, а также выясним, как избежать сопутствующих ошибок блокировок при вызове веб-сервисов.
Для того чтобы понять, почему возникает данная ошибка, проанализируем, как платформа «1С:Предприятие» работает с XML-сообщениями планов обмена. Процесс чтения сообщения состоит из нескольких последовательных этапов:
ПланыОбмена.СоздатьЧтениеСообщения().ЧтениеXML), в который передается XML-данные (из файла или строки).ЧтениеСообщения.НачатьЧтение(ЧтениеXML). На этом этапе платформа считывает технический «конверт» сообщения: заголовок, узел-отправитель, узел-получатель, а также номер принятого сообщения. Указатель (курсор) XML-чтения останавливается строго на начале секции тела сообщения (обычно это узел данных).ПрочитатьXML() или собственных функций разбора.ЧтениеСообщения.ЗакончитьЧтение(), который проверяет целостность структуры 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...» на строке отправки запроса ВебПрокси.НачатьОбмен(ДанныеXDTO).
Давайте разберем физику этого процесса. Эта ошибка возникает по двум основным причинам:
ПередЗаписью() или ПриЗаписи() какого-либо документа или справочника, 1С заблокирует это действие, так как сетевое ожидание ответа сервера может привести к длительным блокировкам СУБД для других пользователей. ПослеЗаписи()) или используйте регламентные задания, работающие вне контекста интерактивной записи объектов.Client сигнализирует о том, что веб-сервер успешно принял запрос, но при выполнении кода метода веб-сервиса внутри центральной базы произошел сбой. Чтобы локализовать эту ошибку, необходимо открыть Журнал регистрации в центральной базе и отфильтровать события по времени вызова веб-сервиса. Там вы обнаружите точную техническую ошибку (например, конфликт блокировок СУБД, ошибку деления на ноль, отсутствие прав доступа или ошибку метаданных).Для создания стабильного и масштабируемого обмена данными между разнородными конфигурациями рекомендуем придерживаться следующих правил:
ЧтениеXML.Прочитать() и контролировать положение курсора. Фабрика XDTO автоматически валидирует и сопоставляет входящие XML-структуры, заполняя свойства объектов.Попытка...Исключение...КонецПопытки. Это позволит продолжить чтение файла обмена, даже если один из объектов пакета поврежден или содержит несовместимые данные.