Как скачать файл в 1С по ссылке-скрипту, в которой не указано расширение

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В веб-разработке и при интеграции систем очень часто возникает задача автоматического скачивания файлов из интернета. Обычные статические файлы скачать достаточно легко: их URL-адрес заканчивается понятным расширением (например, .xlsx, .pdf или .zip). Но что делать, если ссылка ведет на скрипт (например, http://site.ru/get_report.php?id=123 или http://site.ru/?type=logon&id=2), который генерирует файл «на лету» после обработки параметров или авторизации пользователя?

В рамках этой статьи мы подробно разберем, как научить 1С правильно скачивать такие файлы. Мы проанализируем проблемы базовых подходов, изучим правильные методы работы с HTTP-запросами на стороне сервера и рассмотрим нюансы, которые уберегут вашу базу от сбоев и утечек памяти.

Разбор базового решения: эмуляция POST-запроса

Рассмотрим ситуацию, когда файл генерируется сервером по факту отправки формы (сабмита). В этом случае нам необходимо программно воссоздать действия пользователя в браузере. Для этого мы отправляем POST-запрос, передавая параметры формы в теле запроса.

Посмотрим на пример кода, который решает эту задачу с помощью низкоуровневых объектов платформы 1С:


// Определяем адрес ресурса и параметры скрипта
ИмяСервера = "mols.roszdravnadzor.ru";
ПутьРесурса = "/?type=logon&id=2";

// Создаем HTTP-запрос к скрипту
HTTPЗапрос = Новый HTTPЗапрос(ПутьРесурса);

// Настраиваем заголовки, сообщающие серверу формат передаваемых данных
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");
HTTPЗапрос.Заголовки.Вставить("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7");

// Устанавливаем тело запроса (параметры формы, которые обрабатывает скрипт)
HTTPЗапрос.УстановитьТелоИзСтроки("action=export_drugs");

// Инициализируем соединение с сервером
Соединение = Новый HTTPСоединение(ИмяСервера);

// Отправляем запрос методом POST (ОтправитьДляОбработки)
Ответ = Соединение.ОтправитьДляОбработки(HTTPЗапрос);

КодСостояния = Ответ.КодСостояния;
Если КодСостояния = 200 Тогда
    // Получаем данные ответа
    Данные = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.ANSI);
    
    // Записываем полученный текст в файл
    ТекстДокумент = Новый ТекстовыйДокумент;
    ТекстДокумент.УстановитьТекст(Данные);
    ТекстДокумент.Записать("C:\temp\downloaded_file.txt");
Иначе
    ВызватьИсключение "Ошибка сервера. Код состояния: " + КодСостояния;
EndIf;

Этот пример наглядно показывает общую логику процесса. Мы разделяем ссылку на имя сервера и путь к скрипту с параметрами, настраиваем HTTP-заголовки, отправляем запрос и сохраняем результат. Однако данный код имеет ряд серьезных ограничений, которые в реальной практике могут привести к повреждению скачиваемых файлов.

Критическая ошибка: обработка бинарных файлов как строк

В базовом примере тело ответа считывается методом ПолучитьТелоКакСтроку(КодировкаТекста.ANSI). Это допустимо только в том случае, если скрипт генерирует текстовый формат (CSV, XML, HTML, JSON). Если же скрипт возвращает бинарный файл (например, архив ZIP, документ PDF, книгу Excel или картинку PNG), то попытка преобразовать двоичный поток в строку ANSI полностью разрушит структуру файла. Файл запишется на диск, но при попытке его открыть любая программа выдаст ошибку о повреждении структуры.

Для гарантированно корректного сохранения любых типов данных (как текстовых, так и бинарных) необходимо использовать двоичные данные. Рассмотрим два правильных варианта реализации.

Вариант 1. Использование промежуточных двоичных данных в оперативной памяти


// Получаем ответ в виде двоичных данных
ДвоичныеДанные = Ответ.ПолучитьТелоКакДвоичныеДанные();

// Сохраняем файл на диск
ПутьКФайлу = "C:\temp\report.xlsx";
ДвоичныеДанные.Записать(ПутьКФайлу);

Вариант 2. Скачивание напрямую в файл на диске (рекомендуемый способ)

Этот способ является наиболее эффективным, так как он транслирует поток данных из сети непосредственно в файл на диске, минуя хранение всего объема файла в оперативной памяти сервера 1С. Это критически важно при работе регламентных заданий под нагрузкой.


ПутьДляСохранения = "C:\temp\report_direct.xlsx";

// Используем метод Получить объекта HTTPСоединение
// Второй параметр указывает, куда сохранить результат напрямую
Соединение.Получить(HTTPЗапрос, ПутьДляСохранения);

Как автоматически определить реальное имя и расширение файла

Поскольку наш URL-адрес указывает на скрипт (например, get_file.php), мы заранее не знаем, какое расширение у скачиваемого файла. Если мы всегда будем сохранять файл под статичным именем с расширением .txt, операционная система не сможет его открыть правильным приложением.

Веб-сервер при отдаче файла отправляет в заголовках ответа специальный параметр Content-Disposition, в котором содержится реальное имя файла. Нам нужно проанализировать заголовки ответа и вытащить это имя.

Разберем по шагам, как получить реальное имя файла из заголовков:


Ответ = Соединение.ОтправитьДляОбработки(HTTPЗапрос);

ИмяФайла = "default_name.tmp"; // Имя по умолчанию на случай, если заголовок отсутствует

// Ищем заголовок Content-Disposition (регистр может отличаться)
Headers = Ответ.Заголовки;
ЗначениеЗаголовка = "";

Для Каждого КлючИЗначение Из Headers Цикл
    Если ВРег(КлючИЗначение.Ключ) = "CONTENT-DISPOSITION" Тогда
        ЗначениеЗаголовка = КлючИЗначение.Значение;
        Прервать;
    КонецЕсли;
КонецЦикла;

// Если заголовок найден, извлекаем имя файла
Если ЗначениеЗаголовка <> "" Тогда
    // Пример значения заголовка: attachment; filename="report_2026.xlsx"
    ПозицияИмени = Найти(ЗначениеЗаголовка, "filename=");
    Если ПозицияИмени > 0 Тогда
        ИмяФайла = Сред(ЗначениеЗаголовка, ПозицияИмени + Лев(ЗначениеЗаголовка, 9)); // Вырезаем filename=
        ИмяФайла = СтрЗаменить(ИмяФайла, "filename=", "");
        ИмяФайла = СокрЛП(СтрЗаменить(ИмяФайла, """", "")); // Убираем кавычки
    КонецЕсли;
КонецЕсли;

// Теперь мы можем безопасно сохранить файл с правильным именем
ПутьДляСохранения = "C:\temp\" + ИмяФайла;
Ответ.ПолучитьТелоКакДвоичныеДанные().Записать(ПутьДляСохранения);

Обработка HTTP-перенаправлений (Редиректов 3xx)

Часто скрипты генерации файлов работают по следующей схеме: вы отправляете запрос, скрипт подготавливает файл, кладет его во временную папку на веб-сервере и возвращает статус перенаправления (HTTP 301, 302 или 307) со ссылкой на этот временный файл.

Стандартное HTTPСоединение в 1С по умолчанию не переходит по редиректам автоматически. Если скрипт вернул код 302 Found, метод получения данных вернет пустой файл или HTML-страницу заглушки.

Проанализируем правильный алгоритм обработки редиректов в коде 1С:


МаксимальноеКоличествоПеренаправлений = 5;
ТекущийЗапрос = HTTPЗапрос;
СчетчикРедиректов = 0;

Пока СчетчикРедиректов < МаксимальноеКоличествоПеренаправлений Цикл
    Ответ = Соединение.ОтправитьДляОбработки(ТекущийЗапрос);
    КодСостояния = Ответ.КодСостояния;
    
    // Проверяем, является ли статус редиректом (301, 302, 303, 307, 308)
    Если КодСостояния = 301 Или КодСостояния = 302 Или КодСостояния = 303 Или КодСостояния = 307 Или КодСостояния = 308 Тогда
        
        // Получаем новый адрес из заголовка Location
        НовыйАдрес = "";
        Для Каждого КлючИЗначение Из Ответ.Заголовки Цикл
            Если ВРег(КлючИЗначение.Ключ) = "LOCATION" Тогда
                НовыйАдрес = КлючИЗначение.Значение;
                Прервать;
            КонецЕсли;
        КонецЦикла;
        
        Если ЗначениеЗаполнено(НовыйАдрес) Тогда
            // Если адрес абсолютный, нужно пересоздать соединение (если изменился хост)
            // Для упрощения примера предполагаем, что путь относительный или на том же сервере
            ТекущийЗапрос = Новый HTTPЗапрос(НовыйАдрес);
            СчетчикРедиректов = СчетчикРедиректов + 1;
        Иначе
            Прервать; // Заголовок Location пуст, прекращаем работу
        EndIf;
        
    Иначе
        // Если статус не редирект, прерываем цикл и обрабатываем результат
        Прервать;
    КонецЕсли;
КонецЦикла;

Работа с авторизацией и сохранение сессии (Cookie)

Большинство корпоративных порталов не отдадут файл скриптом анонимному пользователю. Сначала нужно пройти авторизацию (обычно методом POST), получить от сервера сессионные куки (Cookies), и только потом с этими куками выполнять запрос на скачивание файла.

Для того чтобы 1С автоматически сохраняла состояние сессии между запросами, при создании объекта HTTPСоединение необходимо явно указывать использование Cookie:


// Включаем использование Cookie в параметрах соединения (четвертый параметр конструктора)
// ИспользоватьCookie = Истина
Соединение = Новый HTTPСоединение("mols.roszdravnadzor.ru", , , , , , , Истина);

// Шаг 1: Авторизуемся на сайте
ЗапросАвторизации = Новый HTTPЗапрос("/auth.php");
ЗапросАвторизации.УстановитьТелоИзСтроки("login=myuser&password=mypassword");
ОтветАвторизации = Соединение.ОтправитьДляОбработки(ЗапросАвторизации);

// Шаг 2: Скачиваем файл. 1С автоматически подставит полученные Cookie авторизации
ЗапросФайла = Новый HTTPЗапрос("/?type=logon&id=2");
ЗапросФайла.УстановитьТелоИзСтроки("action=export_drugs");

ПутьДляСохранения = "C:\temp\secure_report.xlsx";
Соединение.Получить(ЗапросФайла, ПутьДляСохранения);

Почему использование устаревшего COM-объекта Internet Explorer недопустимо

В старых обсуждениях часто можно встретить совет использовать COM-объект Internet Explorer (InternetExplorer.Application) для обхода проблем с авторизацией и автоматического выполнения JS-скриптов генерации.

Рассмотрим, почему от этого подхода нужно полностью отказаться в современных реалиях:

  1. Платформенная зависимость: COM-объекты Windows принципиально не работают на серверах под управлением ОС Linux. Современные серверные кластеры 1С все чаще разворачиваются на Linux.
  2. Прекращение поддержки: Браузер Internet Explorer официально удален Microsoft из состава современных операционных систем.
  3. Нестабильность в фоновых заданиях: Запуск графического движка браузера в сессии регламентного задания на сервере часто приводит к «зависанию» процессов в памяти компьютера и быстрой утечке системных ресурсов сервера.

Если вам необходимо скачать файл, который генерируется сложным клиентским JavaScript-сценарием (SPA-приложения, AJAX-генерация), лучшей современной альтернативой является использование утилиты curl или использование headless-браузеров, запускаемых через системную команду ЗапуститьПриложение.

Интеграция решения с подсистемой БСП «Получение файлов из Интернета»

В типовых конфигурациях 1С присутствует замечательная библиотека БСП, которая содержит готовую функцию ПолучениеФайловИзИнтернета.СкачатьФайлНаСервере(). На самом деле она умеет работать со скриптами, если правильно передать параметры ее вызова.

Основная причина, по которой БСП «не хавает» такие ссылки — блокировка роботов со стороны веб-сервера. Чтобы сервер принял запрос от 1С как от обычного человека, нам нужно подменить заголовок User-Agent. Посмотрим на пример вызова:


ПараметрыПолучения = ПолучениеФайловИзИнтернета.ПараметрыПолученияФайла();

// Задаем заголовки, имитирующие работу обычного браузера Chrome
ДополнительныеЗаголовки = Новый Соответствие;
ДополнительныеЗаголовки.Вставить("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
ДополнительныеЗаголовки.Вставить("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
ПараметрыПолучения.Заголовки = ДополнительныеЗаголовки;

// Разрешаем перенаправления (редиректы)
ПараметрыПолучения.ПереходитьПоРегулярнымВыражениямПеренаправлений = Истина;

СтрПуть = "https://mols.roszdravnadzor.ru/?type=logon&id=2";

// Вызываем штатную процедуру БСП
Результат = ПолучениеФайловИзИнтернета.СкачатьФайлНаСервере(СтрПуть, ПараметрыПолучения);

Если Результат.Статус Тогда
    ИмяСкачанногоФайла = Результат.ПутьКФайлу;
    // Файл успешно сохранен во временный каталог, можем обрабатывать!
Иначе
    Сообщить("Не удалось скачать файл: " + Результат.СообщениеОбОшибке);
КонецЕсли;

Используя этот подход БСП, мы перекладываем на проверенные библиотечные механизмы обработку ошибок сети, прокси-серверов и создание защищенного SSL-соединения, сохраняя при этом гибкость настройки HTTP-заголовков для правильной работы веб-скрипта.

← На главную