Разработка интеграций в устаревшей, но все еще активно используемой платформе 1С 7.7 часто сопряжена с трудностями. В отличие от современной платформы 1С 8.3, где для работы с HTTP-протоколом предусмотрен удобный встроенный класс HTTPСоединение, в "семерке" штатные инструменты работы с сетью отсутствуют или сильно устарели. Особенно остро встает вопрос, когда требуется не просто отправить текстовый SOAP-пакет, но и передать бинарные данные, например ZIP-архив, или настроить сложную авторизацию.
В этой статье мы подробно разберем несколько надежных способов отправки POST-запросов и передачи файлов из 1С 7.7. Мы откажемся от использования устаревших библиотек вроде v7plus.dll и задействуем мощные механизмы операционной системы Windows через COM-соединение.
В типовых конфигурациях 1С 7.7 часто можно встретить встроенную компоненту v7plus.dll и объект V7HttpReader. Настоятельно рекомендуем отказаться от этого инструмента по следующим причинам:
Вместо этого мы будем использовать проверенные системные компоненты Windows: MSXML2.ServerXMLHTTP.6.0 (или MSXML2.XMLHTTP.6.0), WinHttp.WinHttpRequest.5.1 и ADODB.Stream.
Для начала разберем классическую схему отправки 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: " + ОписаниеОшибки(), "!");
Возврат "";
КонецПопытки;
КонецФункции
После получения 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.
Одной из главных проблем 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();
КонецПроцедуры
В некоторых интеграциях веб-сервис требует передавать бинарный 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-запроса.
Иногда современные корпоративные веб-сервисы требуют сложной авторизации по стандарту 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 помните о системных ограничениях на объём используемой памяти. Чтобы минимизировать риски падения программы, следуйте простым правилам:
MSXML2.ServerXMLHTTP.6.0 гораздо стабильнее управляет памятью при работе с большими бинарными пакетами, чем обычный клиентский XMLHTTP.WinHTTP.responseText. Вместо этого сохраняйте входящий поток данных напрямую на диск с помощью метода SaveToFile объекта ADODB.Stream.multipart/form-data) с одновременной передачей файла и текстовых полей заголовков, отличным решением будет отказ от COM-объектов в пользу консольной утилиты cURL. Вы можете сгенерировать команду запуска в 1С 7.7 и выполнить ее через функцию ЗапуститьПриложение(). Утилита cURL полностью возьмет на себя стабильную сетевую работу без ограничений по объему памяти.