Почему V7HttpReader в 1С 7.7 скачивает файл не полностью и как это исправить?

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

При интеграции 1С 7.7 с интернет-ресурсами разработчики часто используют стандартную внешнюю компоненту v7plus.dll и ее объект AddIn.V7HttpReader. Однако при попытке выгрузить файл, который генерируется веб-сервером динамически (например, выгрузка заказов в формате XML за определенный период), возникает неприятная ошибка: метод ПолучитьКакФайл или ПолучитьКакСтроку загружает файл лишь частично. При этом через обычный веб-браузер тот же самый файл скачивается целиком и без каких-либо повреждений.

Разберем детально, почему так происходит, какие ограничения заложены в компоненту V7HttpReader и какими способами можно гарантированно получить файл с сервера целиком.

Выясним причину: почему V7HttpReader обрезает файл

Проанализируем техническую сторону взаимодействия между веб-сервером и компонентой V7HttpReader:

  1. Потоковая передача данных (Chunked Transfer Encoding). Когда файл формируется на сервере «на лету» (скриптами PHP, Python, ASP.NET и т. д.), веб-сервер заранее не знает точный размер итогового ответа. Сервер формирует ответ в режиме Transfer-Encoding: chunked и не передает заголовок Content-Length. Компонента V7HttpReader, построенная на старых функциях библиотеки WinINet, считывает только первый пришедший буфер данных и сразу завершает соединение, полагая, что передача окончена. В то же время браузер умеет склеивать все части ответа до получения завершающего блока.
  2. Сжатие содержимого (Gzip / Deflate). Современные веб-серверы по умолчанию сжимают текстовые потоки (включая XML). Если сервер возвращает сжатый поток, устаревший V7HttpReader не производит его прозрачную распаковку, что приводит к повреждению или усечению файла.
  3. Метод ПолучитьКакФайл предназначен для статики. Если файл статически лежит на диске сервера (сервер знает его точный размер и отдает заголовок Content-Length), метод отрабатывает корректно. Но для динамических POST- и GET-запросов его возможностей недостаточно.

Способ 1. Использование Microsoft.XMLHTTP и ADODB.Stream через ScriptControl

Рассмотрим самое надежное и проверенное решение, не требующее изменения настроек удаленного веб-сервера. Мы используем стандартные системные библиотеки 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-ответа и сохраняет файл в неизменном виде независимо от его размера.

Способ 2. Использование объекта WinHttp.WinHttpRequest.5.1

Разберем еще один вариант решения через серверный компонент 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);
КонецЕсли;

Способ 3. Загрузка через консольную утилиту cURL

Если на рабочей станции возникают проблемы с разрядностью 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 полностью берет на себя поддержку протоколов шифрования, корректно собирает чанки и сохраняет файл в указанную папку.

Способ 4. Корректировка на стороне сервера

Если у нас есть доступ к серверному коду (например, к PHP-скрипту, генерирующему XML), мы можем адаптировать выдачу под ограничения V7HttpReader. Для этого необходимо отключить чанкование и сжатие, а также явно отправить размер ответа:

  1. Сформировать полный текст XML в строковую переменную в оперативной памяти сервера перед началом вывода.
  2. Передать заголовок с точным размером тела в байтах: Content-Length: [размер_в_байтах].
  3. Передать заголовок Content-Type: application/xml; charset=utf-8.
  4. Отключить буферизацию сжатия (gzip/deflate) для этого ответа.

После этого метод V7HttpReader.ПолучитьКакФайл сможет корректно считать весь объем данных, так как будет заранее знать точную границу передаваемого файла.

Когда метод 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.

← На главную