Как реализовать POST-запрос и отправку ZIP-архива через веб-сервис в 1С 7.7

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Разработка интеграций в устаревшей, но все еще активно используемой платформе 1С 7.7 часто сопряжена с трудностями. В отличие от современной платформы 1С 8.3, где для работы с HTTP-протоколом предусмотрен удобный встроенный класс HTTPСоединение, в "семерке" штатные инструменты работы с сетью отсутствуют или сильно устарели. Особенно остро встает вопрос, когда требуется не просто отправить текстовый SOAP-пакет, но и передать бинарные данные, например ZIP-архив, или настроить сложную авторизацию.

В этой статье мы подробно разберем несколько надежных способов отправки POST-запросов и передачи файлов из 1С 7.7. Мы откажемся от использования устаревших библиотек вроде v7plus.dll и задействуем мощные механизмы операционной системы Windows через COM-соединение.

Почему не стоит использовать V7Plus.dll (V7HttpReader)

В типовых конфигурациях 1С 7.7 часто можно встретить встроенную компоненту v7plus.dll и объект V7HttpReader. Настоятельно рекомендуем отказаться от этого инструмента по следующим причинам:

  1. Отсутствие поддержки TLS 1.2/1.3. Компонента не поддерживает современные протоколы шифрования и безопасности, из-за чего попытка подключиться к большинству современных HTTPS-серверов закончится критической ошибкой соединения.
  2. Проблемы с UTF-8. Она некорректно работает с кодировками отличными от Windows-1251, принудительно перекодируя трафик, что мгновенно разрушает структуру отправляемых XML- или JSON-пакетов.

Вместо этого мы будем использовать проверенные системные компоненты Windows: MSXML2.ServerXMLHTTP.6.0 (или MSXML2.XMLHTTP.6.0), WinHttp.WinHttpRequest.5.1 и ADODB.Stream.

Решение 1: Отправка стандартного POST-запроса через XMLHTTP

Для начала разберем классическую схему отправки XML-запроса к веб-сервису. Этот способ отлично подходит для стандартных SOAP-сервисов, где авторизация происходит по схеме Basic Auth (логин и пароль кодируются в формате Base64).

Рассмотрим пример функции, которая формирует объект подключения, задает необходимые HTTP-заголовки и отправляет запрос на сервер:


Функция ОтправитьЗапросНаСервер(Сервер, Порт, Метод, Request, Пользователь = "", Пароль = "")
    Попытка
        // Используем ServerXMLHTTP для стабильной работы на серверах и поддержки TLS
        WinHTTP = СоздатьОбъект("MSXML2.ServerXMLHTTP.6.0");
    Исключение
        Попытка
            WinHTTP = СоздатьОбъект("MSXML2.XMLHTTP.6.0");
        Исключение
            Сообщить("Не удалось инициализировать XMLHTTP-компонент!", "!");
            Возврат "";
        КонецПопытки;
    КонецПопытки;

    АдресРесурса = "https://" + Сервер + ":" + Порт + "/ws/" + Метод;
    
    // Инициализируем POST-запрос (0 - синхронный режим работы)
    WinHTTP.Open("POST", АдресРесурса, 0);
    
    // Устанавливаем необходимые HTTP-заголовки
    WinHTTP.setRequestHeader("Accept-Encoding", "gzip,deflate");
    WinHTTP.setRequestHeader("Content-Type", "application/soap+xml; charset=utf-8");
    WinHTTP.setRequestHeader("SOAPAction", "http://tempuri.org/" + Метод);
    WinHTTP.setRequestHeader("Content-Length", СтрДлина(Request));
    WinHTTP.setRequestHeader("Connection", "Keep-Alive");
    
    // Если требуется базовая авторизация Basic Auth
    Если ПустаяСтрока(Пользователь) = 0 Тогда
        ЛогинПароль = КодироватьBase64(Пользователь + ":" + Пароль);
        WinHTTP.setRequestHeader("Authorization", "Basic " + СокрЛП(ЛогинПароль));
    КонецЕсли;

    Попытка
        // Отправляем сформированное тело запроса
        WinHTTP.send(Request);
        
        // Анализируем полученный статус ответа
        Если Строка(WinHTTP.status) = "200" Тогда
            // Успешно, возвращаем XML-ответ
            Возврат WinHTTP.responseText;
        Иначе
            Сообщить("Ошибка сервера: " + WinHTTP.status + " " + WinHTTP.statusText, "!");
            Возврат "";
        КонецЕсли;
    Исключение
        Сообщить(ОписаниеОшибки(), "!");
        Возврат "";
    КонецПопытки;
КонецФункции

Для работы базовой авторизации нам потребуется функция кодирования строки в формат Base64. В 1С 7.7 нет встроенного метода конвертации строк, но мы можем элегантно реализовать его через системный объект CDO.Message:


Функция КодироватьBase64(ИсходнаяСтрока)
    Попытка
        ЦДО = СоздатьОбъект("CDO.Message");
        БодиПарт = ЦДО.BodyPart;
        Поля = БодиПарт.Fields;
        Поле = Поля.Item("urn:schemas:mailheader:content-type");
        Поле.Value = "text/plain; charset=""windows-1251""";
        Поля.Update();
        
        Стрим = БодиПарт.GetDecodedContentStream();
        Стрим.charset = "windows-1251";
        Стрим.WriteText(ИсходнаяСтрока);
        Стрим.Flush();
        
        БодиПарт.ContentTransferEncoding = "base64";
        СтримДанных = БодиПарт.GetEncodedContentStream();
        Возврат СтримДанных.ReadText();
    Исключение
        Сообщить("Ошибка кодирования Base64: " + ОписаниеОшибки(), "!");
        Возврат "";
    КонецПопытки;
КонецФункции

Решение 2: Обработка полученного XML-ответа

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


Функция ВернутьДОМ(СтрокаXML = "")
    Попытка
        ДОМ = СоздатьОбъект("Msxml2.DOMDocument.6.0");
    Исключение
        ДОМ = СоздатьОбъект("Msxml2.DOMDocument");
    КонецПопытки;
    
    ДОМ.async = 0;
    ДОМ.resolveExternals = 0;
    ДОМ.validateOnParse = 0;
    
    Если СтрокаXML = "" Тогда
        Возврат ДОМ;
    КонецЕсли;
    
    // Загружаем XML-строку и настраиваем язык запросов XPath для поиска узлов
    Попытка
        ДОМ.loadXML(СтрокаXML);
        ДОМ.setProperty("SelectionLanguage", "XPath");
    Исключение
        Сообщить("Ошибка загрузки XML: " + ОписаниеОшибки(), "!");
        ДОМ = 0;
    КонецПопытки;
    
    Возврат ДОМ;
КонецФункции

Теперь мы можем легко находить нужные теги в ответе, используя методы XPath, например, selectSingleNode или selectNodes.

Решение 3: Передача бинарных файлов (ZIP-архивов) через ADODB.Stream

Одной из главных проблем 1С 7.7 является то, что ее внутренний строковый тип данных не предназначен для хранения бинарных (двоичных) данных. Если вы попытаетесь прочитать ZIP-архив в обычную текстовую переменную 1С, то при первой встрече с нулевым байтом (символом с кодом 0) строка будет обрезана, а данные повреждены.

Для решения этой проблемы мы задействуем стандартный системный объект Windows — ADODB.Stream. Он позволяет прочитать любой двоичный файл с диска непосредственно в оперативную память в виде бинарного потока и передать этот поток напрямую в метод send() нашего HTTP-клиента. Операционная система сама корректно упакует байты и передаст их по сети без какого-либо участия строковых переменных 1С.

Рассмотрим, как реализовать отправку ZIP-архива по шагам:


Процедура ОтправитьФайлАрхива(ПутьКФайлу, URL)
    // 1. Создаем и открываем бинарный поток ADODB
    ПотокДанных = СоздатьОбъект("ADODB.Stream");
    ПотокДанных.Type = 1; // 1 означает adTypeBinary (работа с бинарными данными)
    ПотокДанных.Open();
    
    Попытка
        ПотокДанных.LoadFromFile(ПутьКФайлу); // Загружаем наш ZIP-архив с диска
    Исключение
        Сообщить("Не удалось прочитать файл: " + ПутьКФайлу, "!");
        Возврат;
    КонецПопытки;
    
    // 2. Инициализируем HTTP-клиент
    Попытка
        WinHTTP = СоздатьОбъект("MSXML2.ServerXMLHTTP.6.0");
    Исключение
        WinHTTP = СоздатьОбъект("MSXML2.XMLHTTP.6.0");
    КонецПопытки;
    
    WinHTTP.Open("POST", URL, 0);
    
    // Задаем заголовок содержимого как zip-архив
    WinHTTP.setRequestHeader("Content-Type", "application/zip");
    WinHTTP.setRequestHeader("Connection", "Keep-Alive");
    
    // 3. Отправляем поток напрямую в метод send
    Попытка
        Сообщить("Отправка файла на сервер...");
        WinHTTP.send(ПотокДанных);
        
        Если Строка(WinHTTP.status) = "200" Тогда
            Сообщить("Файл успешно передан на веб-сервис!");
        Иначе
            Сообщить("Ошибка при передаче: " + WinHTTP.status + " " + WinHTTP.statusText, "!");
        КонецЕсли;
    Исключение
        Сообщить("Не удалось отправить запрос: " + ОписаниеОшибки(), "!");
    КонецПопытки;
    
    // Обязательно закрываем поток, освобождая файл
    ПотокДанных.Close();
КонецПроцедуры

Решение 4: Кодирование файлов в Base64 прямо внутри XML-пакета

В некоторых интеграциях веб-сервис требует передавать бинарный ZIP-файл не "сырым" потоком, а закодированным в формат Base64 непосредственно внутри XML-тега SOAP-запроса. Если файл достаточно большой, классические функции кодирования (например, через CDO.Message) могут сильно нагружать систему и приводить к зависанию 1С 7.7.

Мы можем использовать невероятно быстрый способ кодирования бинарных файлов в Base64 средствами самого парсера DOMDocument. Он превосходно работает на уровне скомпилированного системного кода Windows. Разберем этот алгоритм:


Функция ПолучитьФайлВBase64(ПутьКФайлу)
    Попытка
        // Считываем файл в бинарный поток
        Поток = СоздатьОбъект("ADODB.Stream");
        Поток.Type = 1; 
        Поток.Open();
        Поток.LoadFromFile(ПутьКФайлу);
        ДвоичныеДанные = Поток.Read();
        Поток.Close();
        
        // Создаем временный элемент XML-документа с типом bin.base64
        DOM = СоздатьОбъект("MSXML2.DOMDocument.6.0");
        Элемент = DOM.createElement("Base64Data");
        Элемент.dataType = "bin.base64";
        Элемент.nodeTypedValue = ДвоичныеДанные;
        
        // Получаем текстовое представление в формате Base64
        РезультатBase64 = Элемент.text;
        
        // Убираем возможные переносы строк, которые генерирует MSXML
        РезультатBase64 = СтрЗаменить(РезультатBase64, Симв(10), "");
        РезультатBase64 = СтрЗаменить(РезультатBase64, Симв(13), "");
        
        Возврат РезультатBase64;
    Исключение
        Сообщить("Ошибка кодирования файла: " + ОписаниеОшибки(), "!");
        Возврат "";
    КонецПопытки;
КонецФункции

Полученную строку Base64 мы можем безопасно вставить в любое место нашего текстового XML-запроса.

Решение 5: Реализация сложной WSSE-авторизации (PasswordDigest)

Иногда современные корпоративные веб-сервисы требуют сложной авторизации по стандарту WSSE (Web Services Security), где передаются динамический хеш пароля, уникальный идентификатор (Nonce) и штамп времени (Created). Математический расчет SHA1-хешей и кодирование в Base64 средствами чистой 1С 7.7 является крайне нетривиальной и медленной задачей.

Для таких случаев разработчики используют подключение внешнего JS-скрипта через объект MSScriptControl.ScriptControl. Сценарий на JavaScript мгновенно производит все необходимые расчеты хэшей и возвращает готовые данные для авторизационного HTTP-заголовка.

Рассмотрим, как инициализировать такой скрипт-контроллер в коде 1С 7.7:


Функция ПолучитьСкриптКонтроллер()
    Попытка
        ScrptCtrl = СоздатьОбъект("MSScriptControl.ScriptControl");
        ScrptCtrl.Language = "javascript";
        
        // Ниже представлен JS-код для генерации WSSE-заголовков
        ТекстФункции = "
        |var hexcase = 0;
        |var b64pad = '=';
        |var chrsz = 8;
        |function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
        |function core_sha1(x, len){
        |  x[len >> 5] |= 0x80 << (24 - len % 32);
        |  x[((len + 64 >> 9) << 4) + 15] = len;
        |  var w = Array(80);
        |  var a =  1732584193;
        |  var b = -271733879;
        |  var c = -1732584194;
        |  var d =  271733878;
        |  var e = -1009455405;
        |  for(var i = 0; i < x.length; i += 16){
        |    var olda = a; var oldb = b; var oldc = c; var oldd = d; var olde = e;
        |    for(var j = 0; j < 80; j++){
        |      if(j < 16) w[j] = x[i + j];
        |      else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
        |      var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j)));
        |      e = d; d = c; c = rol(b, 30); b = a; a = t;
        |    }
        |    a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); e = safe_add(e, olde);
        |  }
        |  return Array(a, b, c, d, e);
        |}
        |function safe_add(x, y){
        |  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
        |  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        |  return (msw << 16) | (lsw & 0xFFFF);
        |}
        |function binb2b64(binarray){
        |  var tab = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        |  var str = '';
        |  for(var i = 0; i < binarray.length * 4; i += 3){
        |    var triplet = (((binarray[i>>2] >> 8 * (3 - i%4)) & 0xFF) << 16)
        |                | (((binarray[i+1>>2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8)
        |                | ((binarray[i+2>>2] >> 8 * (3 - (i+2)%4)) & 0xFF);
        |    for(var j = 0; j < 4; j++){
        |      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
        |      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
        |    }
        |  }
        |  return str;
        |}
        |function str2binb(str){
        |  var bin = Array();
        |  var mask = (1 << chrsz) - 1;
        |  for(var i = 0; i < str.length * chrsz; i += chrsz)
        |    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
        |  return bin;
        |}
        |function sha1_ft(t, b, c, d){
        |  if(t < 20) return (b & c) | ((~b) & d);
        |  if(t < 40) return b ^ c ^ d;
        |  if(t < 60) return (b & c) | (b & d) | (c & d);
        |  return b ^ c ^ d;
        |}
        |function sha1_kt(t){
        |  return (t < 20) ?  1518500249 : (t < 40) ?  1859775393 :
        |         (t < 60) ? -1894007588 : -899497514;
        |}
        |function rol(num, cnt){
        |  return (num << cnt) | (num >>> (32 - cnt));
        |}
        |function encode64(input) {
        |  var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
        |  var output = '';
        |  var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        |  var i = 0;
        |  do {
        |    chr1 = input.charCodeAt(i++);
        |    chr2 = input.charCodeAt(i++);
        |    chr3 = input.charCodeAt(i++);
        |    enc1 = chr1 >> 2;
        |    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        |    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        |    enc4 = chr3 & 63;
        |    if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; }
        |    output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
        |  } while (i < input.length);
        |  return output;
        |}
        |function wsseHeader(Username, Password) {
        |  var timestamp = new Date().toISOString();
        |  var nonce = b64_sha1(timestamp + 'RandomSaltValue');
        |  var nonceEncoded = encode64(nonce);
        |  var digest = b64_sha1(nonce + timestamp + Password);
        |  return 'UsernameToken Username=\"' + Username + '\", PasswordDigest=\"' + digest + '\", Created=\"' + timestamp + '\", Nonce=\"' + nonceEncoded + '\"';
        |}
        |";
        
        ScrptCtrl.AddCode(ТекстФункции);
        Возврат ScrptCtrl;
    Исключение
        Сообщить("Не удалось запустить MSScriptControl: " + ОписаниеОшибки(), "!");
        Возврат 0;
    КонецПопытки;
КонецФункции

Затем в основной процедуре мы можем легко сгенерировать сложную авторизационную строку:


Скрипт = ПолучитьСкриптКонтроллер();
Если ТипЗначенияСтр(Скрипт) = "OLE" Тогда
    СтрокаАвторизации = Скрипт.Run("wsseHeader", ИмяПользователя, ПарольПользователя);
    WinHTTP.setRequestHeader("X-WSSE", СтрокаАвторизации);
КонецЕсли;

Рекомендации по стабильности и производительности

При передаче сверхбольших файлов через HTTP-протокол в 1С 7.7 помните о системных ограничениях на объём используемой памяти. Чтобы минимизировать риски падения программы, следуйте простым правилам:

← На главную