При автоматизации учета продукции, подконтрольной ветеринарному надзору, разработчики на платформе 1С:Предприятие 7.7 часто сталкиваются с вопросом организации обмена с ФГИС «Меркурий» (Ветис.API). В седьмой версии платформы отсутствует встроенная поддержка веб-сервисов, что заставляет искать пути взаимодействия: использовать платные шлюзы, внешние компоненты на .NET или реализовать прямой обмен средствами COM-объектов операционной системы. Разберем, как построить надежный прямой обмен через стандартные системные библиотеки без использования сторонних платных сервисов.
Для взаимодействия с сервисами Ветис.API из 1С 7.7 оптимально использовать связку из двух стандартных системных COM-объектов Windows:
MSXML2.DOMDocument.6.0 — применяется для генерации и разбора SOAP-конвертов и структурированных XML-документов с поддержкой пространств имен (namespaces).WinHttp.WinHttpRequest.5.1 — обеспечивает отправку HTTP POST-запросов к веб-сервису по защищенному протоколу HTTPS.Рассмотрим ключевые технические нюансы, которые необходимо учитывать при реализации протокола:
WinHttpRequest успешно устанавливал соединение, на рабочей станции должен быть включен TLS 1.2, а в параметрах объекта WinHttp необходимо явно активировать флаг безопасности: HTTP.Option(9) = 2048.Authorization: Basic ... с логином и паролем пользователя веб-сервиса (в кодировке Base64). Внутри самого XML-тела SOAP-запроса дополнительно передаются параметры apiKey, serviceId, issuerId и логин инициатора.Content-Type: text/xml; charset=utf-8 и SOAPAction: "".submitApplicationRequest возвращает идентификатор заявки (applicationId). Далее с интервалом в 3–5 секунд отправляется запрос receiveApplicationResultRequest для проверки статуса готовности и получения итогового GUID созданного объекта.Проанализируем практический пример функции на встроенном языке 1С 7.7. В коде формируется SOAP-пакет для метода регистрации новой площадки хозяйствующего субъекта (ModifyEnterpriseOperation) с корректным объявлением пространств имен через объект Msxml2.MXNamespaceManager.6.0.
Функция ДобавитьПлощадку(АдресСервераДляЗапроса)
ТекущаяОперация = "Добавление новой площадки ХС";
ДобавитьВЛог("" + ТекущееВремя() + " Начало выполнения операции: " + ТекущаяОперация);
рд_тзРезультат.Очистить();
рд_тзРезультат.НоваяКолонка("Название");
рд_тзРезультат.НоваяКолонка("ИНН");
рд_тзРезультат.НоваяКолонка("Адрес");
рд_тзРезультат.НоваяКолонка("GUID");
рд_тзРезультат.НоваяКолонка("GUIDПредприятия", , , "GUID Предприятия");
XML_DOM = CreateObject("MSXML2.DOMDocument.6.0");
XML_HELPER = CreateObject("Msxml2.MXNamespaceManager.6.0");
XML_HELPER.declarePrefix("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
XML_HELPER.declarePrefix("base", "http://api.vetrf.ru/schema/cdm/base");
XML_HELPER.declarePrefix("ws", "http://api.vetrf.ru/schema/cdm/application/ws-definitions");
XML_HELPER.declarePrefix("app", "http://api.vetrf.ru/schema/cdm/application");
XML_HELPER.declarePrefix("merc", "http://api.vetrf.ru/schema/cdm/mercury/applications");
XML_HELPER.declarePrefix("com", "http://api.vetrf.ru/schema/cdm/argus/common");
XML_HELPER.declarePrefix("ent", "http://api.vetrf.ru/schema/cdm/cerberus/enterprise");
XML_HELPER.declarePrefix("ikar", "http://api.vetrf.ru/schema/cdm/ikar");
Header = XML_DOM.createProcessingInstruction("xml", "version=""1.0"" encoding=""UTF-8""");
XML_DOM.appendChild(Header);
XML_ROOT = ДобавитьУзел(XML_DOM, XML_HELPER, XML_DOM, "Envelope", "soapenv");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:base", "http://api.vetrf.ru/schema/cdm/base");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:ws", "http://api.vetrf.ru/schema/cdm/application/ws-definitions");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:app", "http://api.vetrf.ru/schema/cdm/application");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:merc", "http://api.vetrf.ru/schema/cdm/mercury/applications");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:com", "http://api.vetrf.ru/schema/cdm/argus/common");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:ent", "http://api.vetrf.ru/schema/cdm/cerberus/enterprise");
ДобавитьАттрибут(XML_DOM, XML_ROOT, "xmlns:ikar", "http://api.vetrf.ru/schema/cdm/ikar");
XML_Header = ДобавитьУзел(XML_DOM, XML_HELPER, XML_ROOT, "Header", "soapenv");
XML_Body = ДобавитьУзел(XML_DOM, XML_HELPER, XML_ROOT, "Body", "soapenv");
XML_getBusinessEntityListRequest = ДобавитьУзел(XML_DOM, XML_HELPER, XML_Body, "submitApplicationRequest", "ws");
XML_apiKey = ДобавитьУзел(XML_DOM, XML_HELPER, XML_getBusinessEntityListRequest, "apiKey", "ws");
ДобавитьТекст(XML_DOM, XML_apiKey, СокрЛП(рд_APIKey_Тест));
XML_application = ДобавитьУзел(XML_DOM, XML_HELPER, XML_getBusinessEntityListRequest, "application", "app");
XML_serviceId = ДобавитьУзел(XML_DOM, XML_HELPER, XML_application, "serviceId", "app");
ДобавитьТекст(XML_DOM, XML_serviceId, СокрЛП(рд_ServiceID_Тест));
XML_issuerId = ДобавитьУзел(XML_DOM, XML_HELPER, XML_application, "issuerId", "app");
ДобавитьТекст(XML_DOM, XML_issuerId, СокрЛП(рд_IssuerID_Тест));
XML_issueDate = ДобавитьУзел(XML_DOM, XML_HELPER, XML_application, "issueDate", "app");
СтрДата = Формат(ТекущаяДата(), "ДДДММГГГГ");
СтрДата = Прав(СтрДата, 4) + "-" + Сред(СтрДата, 4, 2) + "-" + Лев(СтрДата, 2) + "T10:00:00";
ДобавитьТекст(XML_DOM, XML_issueDate, СокрЛП(СтрДата));
XML_data = ДобавитьУзел(XML_DOM, XML_HELPER, XML_application, "data", "app");
XML_modifyEnterpriseRequest = ДобавитьУзел(XML_DOM, XML_HELPER, XML_data, "modifyEnterpriseRequest", "merc");
XML_localTransactionId = ДобавитьУзел(XML_DOM, XML_HELPER, XML_modifyEnterpriseRequest, "localTransactionId", "merc");
ДобавитьТекст(XML_DOM, XML_localTransactionId, "a00001");
XML_initiator = ДобавитьУзел(XML_DOM, XML_HELPER, XML_modifyEnterpriseRequest, "initiator", "merc");
XML_login = ДобавитьУзел(XML_DOM, XML_HELPER, XML_initiator, "login", "com");
ДобавитьТекст(XML_DOM, XML_login, СокрЛП(рд_ЛогинМеркурий_Тест));
XML_modificationOperation = ДобавитьУзел(XML_DOM, XML_HELPER, XML_modifyEnterpriseRequest, "modificationOperation", "merc");
XML_type = ДобавитьУзел(XML_DOM, XML_HELPER, XML_modificationOperation, "type", "ent");
ДобавитьТекст(XML_DOM, XML_type, "CREATE");
XML_resultingList = ДобавитьУзел(XML_DOM, XML_HELPER, XML_modificationOperation, "resultingList", "ent");
ДобавитьАттрибут(XML_DOM, XML_resultingList, "count", "1");
ДобавитьАттрибут(XML_DOM, XML_resultingList, "total", "1");
ДобавитьАттрибут(XML_DOM, XML_resultingList, "offset", "0");
XML_enterprise = ДобавитьУзел(XML_DOM, XML_HELPER, XML_resultingList, "enterprise", "ent");
XML_name = ДобавитьУзел(XML_DOM, XML_HELPER, XML_enterprise, "name", "ent");
ДобавитьТекст(XML_DOM, XML_name, "ООО Союз магазин Пролетарский");
XML_type = ДобавитьУзел(XML_DOM, XML_HELPER, XML_enterprise, "type", "ent");
ДобавитьТекст(XML_DOM, XML_type, "1");
XML_address = ДобавитьУзел(XML_DOM, XML_HELPER, XML_enterprise, "address", "ent");
XML_country = ДобавитьУзел(XML_DOM, XML_HELPER, XML_address, "country", "ikar");
XML_guid = ДобавитьУзел(XML_DOM, XML_HELPER, XML_country, "guid", "base");
ДобавитьТекст(XML_DOM, XML_guid, СокрЛП(рд_Фильтр3));
XML_region = ДобавитьУзел(XML_DOM, XML_HELPER, XML_address, "region", "ikar");
XML_guid = ДобавитьУзел(XML_DOM, XML_HELPER, XML_region, "guid", "base");
ДобавитьТекст(XML_DOM, XML_guid, СокрЛП(рд_Фильтр4));
XML_locality = ДобавитьУзел(XML_DOM, XML_HELPER, XML_address, "locality", "ikar");
XML_guid = ДобавитьУзел(XML_DOM, XML_HELPER, XML_locality, "guid", "base");
ДобавитьТекст(XML_DOM, XML_guid, СокрЛП(рд_Фильтр5));
XML_Street = ДобавитьУзел(XML_DOM, XML_HELPER, XML_address, "street", "ikar");
XML_guid = ДобавитьУзел(XML_DOM, XML_HELPER, XML_Street, "guid", "base");
ДобавитьТекст(XML_DOM, XML_guid, СокрЛП(рд_Фильтр8));
XML_addressView = ДобавитьУзел(XML_DOM, XML_HELPER, XML_address, "addressView", "ikar");
ДобавитьТекст(XML_DOM, XML_addressView, СокрЛП(рд_Фильтр6));
XML_activityList = ДобавитьУзел(XML_DOM, XML_HELPER, XML_enterprise, "activityList", "ent");
ДобавитьАттрибут(XML_DOM, XML_activityList, "count", "1");
ДобавитьАттрибут(XML_DOM, XML_activityList, "total", "1");
ДобавитьАттрибут(XML_DOM, XML_activityList, "offset", "0");
XML_activity = ДобавитьУзел(XML_DOM, XML_HELPER, XML_activityList, "activity", "ent");
XML_name = ДобавитьУзел(XML_DOM, XML_HELPER, XML_activity, "name", "ent");
ДобавитьТекст(XML_DOM, XML_name, "Реализация пищевых продуктов");
XML_owner = ДобавитьУзел(XML_DOM, XML_HELPER, XML_enterprise, "owner", "ent");
XML_guid = ДобавитьУзел(XML_DOM, XML_HELPER, XML_owner, "guid", "base");
ДобавитьТекст(XML_DOM, XML_guid, СокрЛП(рд_Фильтр7));
XML_reason = ДобавитьУзел(XML_DOM, XML_HELPER, XML_modificationOperation, "reason", "ent");
ДобавитьТекст(XML_DOM, XML_reason, "Причина добавления тест");
спРезультат = ВыгрузитьDOM(XML_DOM, АдресСервераДляЗапроса, ТекущаяОперация);
КонецФункции
Разберем логику вспомогательной функции отправки ВыгрузитьDOM. Чтобы не повредить кодировку UTF-8 при передаче кириллицы из 1С 7.7, в метод Send() следует передавать непосредственно объект XML_DOM (или двоичный поток ADODB.Stream), избегая предварительного преобразования в обычную строку 1С:
Функция ВыгрузитьDOM(XML_DOM, АдресСервера, ОписаниеОперации)
Попытка
HTTP = CreateObject("WinHttp.WinHttpRequest.5.1");
HTTP.Option(9, 2048); // Включение TLS 1.2
HTTP.Open("POST", АдресСервера, 0);
HTTP.SetRequestHeader("Content-Type", "text/xml; charset=utf-8");
HTTP.SetRequestHeader("SOAPAction", """""");
// Передача заголовка авторизации Basic Auth
// Заголовок формируется как "Basic " + Base64(ЛогинВебСервиса + ":" + ПарольВебСервиса)
HTTP.SetRequestHeader("Authorization", "Basic " + СокрЛП(рд_ТокенАвторизации));
HTTP.Send(XML_DOM);
Если HTTP.Status = 200 Тогда
XML_Ответ = CreateObject("MSXML2.DOMDocument.6.0");
XML_Ответ.loadXML(HTTP.ResponseText);
// Получаем applicationId для асинхронного опроса результата
УзелAppId = XML_Ответ.selectSingleNode("//*[local-name()='applicationId']");
Если ПустоеЗначение(УзелAppId) = 0 Тогда
Возврат УзелAppId.text;
Иначе
Возврат "Ошибка: Не найден applicationId в ответе";
КонецЕсли;
Иначе
Сообщить("Ошибка HTTP: " + HTTP.Status + " " + HTTP.StatusText);
Сообщить(HTTP.ResponseText);
Возврат "";
КонецЕсли;
Исключение
Сообщить("Исключение при вызове веб-сервиса: " + ОписаниеОшибки());
Возврат "";
КонецПопытки;
КонецФункции
BEA-382513: OSB Replace action failed... Error parsing XML: expected exactly one item, got 0 items чаще всего свидетельствует об отправке запроса не на тот конечный адрес веб-сервиса (endpoint) или о нарушении обязательной структуры иерархии тегов внутри блока application/data.MSXML2.DOMDocument полностью загружает XML-дерево в оперативную память. Для предотвращения переполнения памяти в 32-разрядном процессе 1С 7.7 для чтения тяжелых ответов рекомендуется применять потоковый парсер MSXML2.SAXXMLReader60.