При интеграции 1С 7.7 с интернет-ресурсами разработчики часто используют стандартную внешнюю компоненту v7plus.dll и ее объект AddIn.V7HttpReader. Однако при попытке выгрузить файл, который генерируется веб-сервером динамически (например, выгрузка заказов в формате XML за определенный период), возникает неприятная ошибка: метод ПолучитьКакФайл или ПолучитьКакСтроку загружает файл лишь частично. При этом через обычный веб-браузер тот же самый файл скачивается целиком и без каких-либо повреждений.
Разберем детально, почему так происходит, какие ограничения заложены в компоненту V7HttpReader и какими способами можно гарантированно получить файл с сервера целиком.
Проанализируем техническую сторону взаимодействия между веб-сервером и компонентой V7HttpReader:
Transfer-Encoding: chunked и не передает заголовок Content-Length. Компонента V7HttpReader, построенная на старых функциях библиотеки WinINet, считывает только первый пришедший буфер данных и сразу завершает соединение, полагая, что передача окончена. В то же время браузер умеет склеивать все части ответа до получения завершающего блока.V7HttpReader не производит его прозрачную распаковку, что приводит к повреждению или усечению файла.Content-Length), метод отрабатывает корректно. Но для динамических POST- и GET-запросов его возможностей недостаточно.Рассмотрим самое надежное и проверенное решение, не требующее изменения настроек удаленного веб-сервера. Мы используем стандартные системные библиотеки Windows: Microsoft.XMLHTTP для выполнения HTTP-запроса и ADODB.Stream для сохранения бинарных данных на диск.
Важная особенность 1С 7.7: встроенный язык платформы 7.7 не поддерживает работу с сырыми бинарными массивами (тип VT_ARRAY | VT_UI1, который возвращает свойство responseBody). Если попытаться передать полученное тело ответа напрямую в ADODB.Stream методами 1С, возникнет ошибка несоответствия типов. Чтобы обойти это платформенное ограничение, связывание объектов выполняется через легковесный скриптовый движок MSScriptControl.ScriptControl на языке VBScript.
Посмотрим на практическую реализацию загрузки файла:
Скрипт = СоздатьОбъект("MSScriptControl.ScriptControl");
Скрипт.language = "vbscript";
// Создаем HTTP-клиент
x = CreateObject("Microsoft.XMLHTTP");
x.Open("GET", АдресСтраницы, 0, "admin", "Tester1");
x.Send();
// Создаем бинарный поток ADODB
s = CreateObject("ADODB.Stream");
s.Mode = 3; // adModeReadWrite
s.Type = 1; // adTypeBinary
s.Open();
// Передаем объекты в контекст VBScript для записи сырых байтов
Скрипт.AddObject("s", s);
Скрипт.AddObject("x", x);
Скрипт.Eval("s.Write(x.responseBody)");
// Сохраняем полученный файл на диск (2 - adSaveCreateOverWrite)
s.SaveToFile(ИмяФайлаЗагрузки, 2);
s.Close();
Этот подход гарантирует корректную сборку фрагментированного HTTP-ответа и сохраняет файл в неизменном виде независимо от его размера.
Разберем еще один вариант решения через серверный компонент WinHttp.WinHttpRequest.5.1. В отличие от клиентского Microsoft.XMLHTTP, компонент WinHttp не зависит от кэша браузера Internet Explorer, более стабилен в фоновых процессах и поддерживает тонкую настройку сетевых таймаутов.
Посмотрим на пример реализации:
Скрипт = СоздатьОбъект("MSScriptControl.ScriptControl");
Скрипт.language = "vbscript";
WinHttp = CreateObject("WinHttp.WinHttpRequest.5.1");
// Настройка таймаутов: Resolve, Connect, Send, Receive (в миллисекундах)
WinHttp.SetTimeouts(30000, 30000, 30000, 60000);
WinHttp.Open("GET", АдресСтраницы, 0);
// Передача авторизации при необходимости
WinHttp.SetCredentials("admin", "Tester1", 0);
WinHttp.Send();
Если WinHttp.Status = 200 Тогда
s = CreateObject("ADODB.Stream");
s.Mode = 3;
s.Type = 1;
s.Open();
Скрипт.AddObject("s", s);
Скрипт.AddObject("x", WinHttp);
Скрипт.Eval("s.Write(x.responseBody)");
s.SaveToFile(ИмяФайлаЗагрузки, 2);
s.Close();
Иначе
Сообщить("Ошибка загрузки: " + WinHttp.Status + " " + WinHttp.StatusText);
КонецЕсли;
Если на рабочей станции возникают проблемы с разрядностью COM-объектов или версиями протокола безопасности TLS (например, сайт требует TLS 1.2 или TLS 1.3, которые могут не поддерживаться старыми системными компонентами Windows XP/2003), эффективным выходом становится использование утилиты curl.exe.
Разберем вызов консольного загрузчика через объект WScript.Shell:
КоманднаяСтрока = "curl.exe -s -k -u ""admin:Tester1"" """ + АдресСтраницы + """ -o """ + ИмяФайлаЗагрузки + """";
Шелл = СоздатьОбъект("WScript.Shell");
КодВозврата = Шелл.Run(КоманднаяСтрока, 0, 1);
Если КодВозврата = 0 Тогда
Сообщить("Файл успешно загружен!");
Иначе
Сообщить("Ошибка при вызове cURL. Код: " + КодВозврата);
КонецЕсли;
Утилита curl полностью берет на себя поддержку протоколов шифрования, корректно собирает чанки и сохраняет файл в указанную папку.
Если у нас есть доступ к серверному коду (например, к PHP-скрипту, генерирующему XML), мы можем адаптировать выдачу под ограничения V7HttpReader. Для этого необходимо отключить чанкование и сжатие, а также явно отправить размер ответа:
Content-Length: [размер_в_байтах].Content-Type: application/xml; charset=utf-8.После этого метод V7HttpReader.ПолучитьКакФайл сможет корректно считать весь объем данных, так как будет заранее знать точную границу передаваемого файла.
Проанализируем типовую ситуацию, когда компонента V7HttpReader работает без сбоев. Если адрес указывает на реально существующий статичный файл на диске веб-сервера, загрузка проходит успешно.
Рассмотрим пример правильной структуры работы со статикой:
Если ЗагрузитьВнешнююКомпоненту("v7plus.dll") <> 1 Тогда
Предупреждение("Компонента v7plus.dll не найдена!");
Возврат;
КонецЕсли;
Соединение = СоздатьОбъект("AddIn.V7HttpReader");
Соединение.КоличествоПопытокАвторизации = 3;
ФайлНаСервере = "http://mysite.ru/files/katalog.xml";
ВремФайл = КаталогВременныхФайлов() + "katalog.xml";
Попытка
// Метод подходит исключительно для статичных файлов с известным Content-Length
Соединение.ПолучитьКакФайл(ФайлНаСервере, ВремФайл);
Исключение
Сообщить("Неудачная попытка соединения.");
Сообщить(Соединение.СтрокаСостоянияОтвета);
Возврат;
КонецПопытки;
Если ФС.СуществуетФайл(ВремФайл) = 1 Тогда
Сообщить("Файл успешно получен и готов к разбору.");
КонецЕсли;
Таким образом, для получения статических файлов допустимо использовать V7HttpReader, а при работе с динамическими веб-страницами и API-сервисами следует применять связку Microsoft.XMLHTTP (или WinHttp) с ADODB.Stream.