Скачивание файла через HTTPСоединение в 1С 8.3: от запроса до сохранения на диск
Содержание
Чтобы получить файл по HTTP в 1С 8.3, создайте объект HTTPСоединение с адресом сервера, объект HTTPЗапрос с путём к файлу и вызовите Соединение.Получить(Запрос, ИмяФайла). Второй параметр сразу пишет тело ответа на диск, без загрузки в память. После вызова проверьте Ответ.КодСостояния: файл годен только при коде 200.
Ниже рабочий код для HTTP и HTTPS, разбор скачивания в поток и во временное хранилище, обработка редиректов и тексты типовых ошибок вроде Couldn't resolve host name и SSL connect error. Код писался под платформу 8.3 и выполняется на сервере, поэтому подходит и для файловой, и для клиент-серверной базы.
Как устроено HTTP-соединение в 1С
Платформа разделяет запрос на три объекта. Каждый отвечает за своё, и путаница между ними даёт половину вопросов на форумах.
| Объект | За что отвечает | Что в него передают |
|---|---|---|
HTTPСоединение | Связь с конкретным сервером | Хост, порт, логин, пароль, прокси, таймаут, SSL |
HTTPЗапрос | Что именно просим у сервера | Путь к ресурсу и заголовки |
HTTPОтвет | Что сервер вернул | Код состояния, заголовки, тело |
Главное правило: в HTTPСоединение передаётся только имя хоста, без https:// и без пути. Адрес https://example.ru/files/price.xlsx разбивается так:
- 🌐 хост:
example.ru; - 🔒 схема
https→ нужен объектЗащищенноеСоединениеOpenSSLи порт 443; - 📁 путь:
/files/price.xlsx— он уходит вHTTPЗапрос.
Одно соединение можно использовать для нескольких запросов к тому же серверу. Если скачиваете сто файлов с одного сайта, не создавайте сто соединений.
Где выполнять код
HTTPСоединение есть на сервере и в тонком клиенте, но в веб-клиенте его нет. Поэтому скачивание пишут в серверной процедуре (&НаСервере, &НаСервереБезКонтекста) или в общем модуле с флажком «Сервер». На клиент отдают уже готовые двоичные данные.
В клиент-серверном варианте файл появляется на диске сервера 1С, а не на компьютере пользователя. Путь C:\Temp\price.xlsx в серверном коде — это папка на сервере, и у пользователя, от которого запущена служба, должны быть права на запись туда.
Запрос к серверу: минимальный рабочий код
Начнём с разбора адреса. Писать хост и путь руками неудобно, проще одна функция, которая режет URL на части.
&НаСервереБезКонтекста
Функция РазобратьURL(Знач URL)
Результат = Новый Структура("Схема, Хост, Порт, Путь", "http", "", 80, "/");
Поз = СтрНайти(URL, "://");
Если Поз > 0 Тогда
Результат.Схема = НРег(Лев(URL, Поз - 1));
URL = Сред(URL, Поз + 3);
КонецЕсли;
Если Результат.Схема = "https" Тогда
Результат.Порт = 443;
КонецЕсли;
Поз = СтрНайти(URL, "/");
Если Поз > 0 Тогда
Результат.Хост = Лев(URL, Поз - 1);
Результат.Путь = Сред(URL, Поз);
Иначе
Результат.Хост = URL;
КонецЕсли;
Поз = СтрНайти(Результат.Хост, ":");
Если Поз > 0 Тогда
Результат.Порт = Число(Сред(Результат.Хост, Поз + 1));
Результат.Хост = Лев(Результат.Хост, Поз - 1);
КонецЕсли;
Возврат Результат;
КонецФункции
Теперь само скачивание. Функция принимает URL и имя файла, возвращает код ответа.
&НаСервереБезКонтекста
Функция СкачатьФайл(URL, ИмяФайла)
Части = РазобратьURL(URL);
SSL = Неопределено;
Если Части.Схема = "https" Тогда
SSL = Новый ЗащищенноеСоединениеOpenSSL();
КонецЕсли;
Таймаут = 60; // секунд; 0 — ждать бесконечно
Соединение = Новый HTTPСоединение(Части.Хост, Части.Порт, , , , Таймаут, SSL);
Заголовки = Новый Соответствие;
Заголовки.Вставить("User-Agent", "Mozilla/5.0 (1C-Enterprise)");
Заголовки.Вставить("Accept", "*/*");
Запрос = Новый HTTPЗапрос(Части.Путь, Заголовки);
Ответ = Соединение.Получить(Запрос, ИмяФайла);
Возврат Ответ.КодСостояния;
КонецФункции
Порядок параметров конструктора: сервер, порт, пользователь, пароль, прокси, таймаут, защищённое соединение. Пустые запятые пропускают ненужные.
Заголовки, авторизация и прокси
Часть серверов отдаёт 403 запросу без заголовка User-Agent, поэтому он стоит в примере сразу. Остальное добавляйте по ситуации:
- 🔑 Basic-авторизация — логин и пароль третьим и четвёртым параметром
HTTPСоединение; - 🎫 токен — заголовок
Authorizationсо значением"Bearer " + Токен; - 🧭 прокси — объект
ИнтернетПрокси, пятый параметр соединения.
Прокси = Новый ИнтернетПрокси(Ложь);
Прокси.Установить("https", "proxy.local", 3128, "user", "pass");
Соединение = Новый HTTPСоединение(Хост, 443, , , Прокси, 60, Новый ЗащищенноеСоединениеOpenSSL());
Новый ИнтернетПрокси(Истина) берёт системные настройки прокси. В типовых конфигурациях на БСП прокси уже настроен в программе, и его получают функцией ПолучениеФайловИзИнтернета.ПолучитьПрокси("https").
Кириллицу и пробелы в пути кодируйте: КодироватьСтроку(ИмяНаСайте, СпособКодированияСтроки.КодировкаURL). Кодируйте только имя файла или параметр, а не весь адрес целиком, иначе испортятся слэши.
Как сохранить полученный файл: три способа
Тело ответа можно забрать по-разному. Выбор зависит от размера файла и от того, что с ним делать дальше.
| Способ | Как вызвать | Когда подходит | Минус |
|---|---|---|---|
| Сразу в файл | Получить(Запрос, ИмяФайла) | Большие файлы, архивы, выгрузки | Нужна папка с правами на запись |
| В память | Ответ.ПолучитьТелоКакДвоичныеДанные() | Мелкие файлы до десятков мегабайт | Весь файл в оперативной памяти |
| Потоком | Ответ.ПолучитьТелоКакПоток() | Нужно обработать данные по частям | Код длиннее |
Сохранение сразу в файл
Это самый экономный путь. Платформа пишет байты на диск по мере получения, и гигабайтный архив не займёт гигабайт памяти рабочего процесса. После вызова Ответ.ПолучитьИмяФайлаТела() вернёт путь, куда лёг файл.
Файл создаётся при любом коде ответа. Если сервер вернул страницу 404 Not Found, на диске окажется HTML с текстом ошибки под именем price.xlsx. Проверяйте код до того, как отдавать файл дальше.
Получение в память
Удобно, когда файл нужно сразу положить в реквизит, присоединённый файл или временное хранилище.
Ответ = Соединение.Получить(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Данные = Ответ.ПолучитьТелоКакДвоичныеДанные();
Данные.Записать("C:\Exchange\price.xlsx");
КонецЕсли;
Текстовые ответы (CSV, JSON, XML) читайте через Ответ.ПолучитьТелоКакСтроку("UTF-8"). Если кодировку не указать, платформа попробует взять её из заголовка Content-Type, а при его отсутствии русские буквы могут превратиться в «кракозябры».
Работа с потоком
Поток нужен, когда данные идут дальше по цепочке: в архиватор, в чтение построчно, в расчёт хеша. Пример копирования потока ответа в файловый поток:
ПотокОтвета = Ответ.ПолучитьТелоКакПоток();
ФайлНаДиске = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
ПотокОтвета.КопироватьВ(ФайлНаДиске);
ФайлНаДиске.Закрыть();
ПотокОтвета.Закрыть();
Закрывайте оба потока. Незакрытый файловый поток держит файл, и следующая попытка его удалить или перезаписать упадёт с ошибкой доступа.
Как узнать настоящее имя и тип файла
Если адрес вида /download?id=845, имя файла приходит в заголовке ответа. Посмотрите в Ответ.Заголовки:
- 📄
Content-Disposition— строка видаattachment; filename="report.pdf", из неё вырезают имя; - 🏷
Content-Type— тип содержимого:application/pdf,application/zip,text/html; - 📏
Content-Length— размер в байтах, им удобно сверить, что файл докачался целиком.
Регистр имён заголовков у разных серверов разный, поэтому ищите ключ без учёта регистра: переберите соответствие и сравнивайте НРег(КлючИЗначение.Ключ).
Тип text/html вместо ожидаемого application/pdf — верный признак, что вместо файла пришла страница входа или заглушка.
Временные файлы и передача пользователю
На сервере файл обычно скачивают во временный каталог. Имя берут функцией ПолучитьИмяВременногоФайла("xlsx"): она возвращает уникальный путь в каталоге временных файлов платформы с нужным расширением.
&НаСервере
Функция ПолучитьФайлДляПользователя(URL)
ИмяВременного = ПолучитьИмяВременногоФайла("xlsx");
Код = СкачатьФайл(URL, ИмяВременного);
Если Код <> 200 Тогда
УдалитьФайлы(ИмяВременного);
ВызватьИсключение СтрШаблон("Сервер вернул код %1", Формат(Код, "ЧГ=0"));
КонецЕсли;
Данные = Новый ДвоичныеДанные(ИмяВременного);
УдалитьФайлы(ИмяВременного);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Функция возвращает адрес во временном хранилище. Дальше клиент сохраняет файл на компьютер пользователя. В конфигурациях на БСП для этого есть ФайловаяСистемаКлиент.СохранитьФайл(Оповещение, Адрес, "price.xlsx"): процедура сама покажет диалог сохранения и работает в тонком и веб-клиенте.
Временный файл удаляйте сразу, как только прочитали его в ДвоичныеДанные. Платформа не чистит такие файлы за вас, и на загруженном сервере каталог Temp за месяц разрастается до гигабайт.
Привязка хранилища к УникальныйИдентификатор формы важна: данные живут, пока открыта форма, и освобождаются после её закрытия. Без идентификатора они лежат до конца сеанса.
Готовая функция БСП
В типовых конфигурациях (Бухгалтерия 3.0, ЗУП 3.1, УТ 11 и другие на БСП) есть общий модуль ПолучениеФайловИзИнтернета. Его функция СкачатьФайлНаСервере(URL, ПараметрыПолучения) сама учитывает настройки прокси программы и возвращает структуру со статусом и путём к файлу. В доработках типовой конфигурации лучше пользоваться ею, чем писать своё соединение.
Коды ответа и обработка ошибок
Ошибки бывают двух видов, и ловят их по-разному.
- Соединение не установлено — нет сети, неверный хост, сбой SSL, вышел таймаут. Платформа вызывает исключение, кода ответа нет вовсе.
- Сервер ответил, но не файлом — код 404, 403, 500. Исключения нет,
Получить()отработал «успешно».
Поэтому вызов оборачивают в Попытка, а результат проверяют по коду:
Попытка
Ответ = Соединение.Получить(Запрос, ИмяФайла);
Исключение
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("ЗагрузкаФайлов", УровеньЖурналаРегистрации.Ошибка, , URL, ТекстОшибки);
Возврат Неопределено;
КонецПопытки;
Запись в журнал регистрации спасает при регламентных заданиях: пользователь сообщения не увидит, а администратор найдёт причину по событию ЗагрузкаФайлов.
Что означают коды состояния
| Код | Что случилось | Что делать |
|---|---|---|
| 200 | Файл отдан | Работать с файлом |
| 301, 302, 307, 308 | Файл переехал | Взять адрес из заголовка Location и повторить |
| 401 | Нужна авторизация | Передать логин и пароль или токен |
| 403 | Доступ запрещён | Проверить User-Agent, права, IP-ограничения |
| 404 | Файла по адресу нет | Проверить путь и регистр букв в нём |
| 429 | Слишком частые запросы | Сделать паузу и повторить позже |
| 500, 502, 503 | Сбой на стороне сайта | Повторить через несколько минут |
Редиректы
HTTPСоединение не переходит по перенаправлению сам. Сайт отвечает 302, и вы получаете пустой ответ вместо файла. Обрабатывайте это циклом с ограничением на число переходов:
Для НомерПерехода = 1 По 5 Цикл
Ответ = Соединение.Получить(Запрос, ИмяФайла);
Если Ответ.КодСостояния < 300 Или Ответ.КодСостояния >= 400 Тогда
Прервать;
КонецЕсли;
НовыйАдрес = Ответ.Заголовки.Получить("Location");
Если НовыйАдрес = Неопределено Тогда
НовыйАдрес = Ответ.Заголовки.Получить("location");
КонецЕсли;
Если НовыйАдрес = Неопределено Тогда
Прервать;
КонецЕсли;
// пересоздать Соединение и Запрос по НовыйАдрес через РазобратьURL
КонецЦикла;
Адрес в Location бывает относительным (/files/new.xlsx). Тогда хост остаётся прежним, меняется только путь в запросе. Если хост или схема поменялись, соединение придётся создать заново.
Ограничение в пять переходов защищает от зацикливания, когда сайт гоняет запрос между двумя адресами.
Типичные ошибки при скачивании и что с ними делать
Тексты ошибок соединения приходят из сетевой библиотеки платформы на английском. По ним причина определяется почти однозначно.
https:// или путь, и что с сервера 1С открывается этот сайт: DNS на сервере может отличаться от вашего компьютера.ЗащищенноеСоединениеOpenSSL при адресе https или указали порт 80 вместо 443.ЗащищенноеСоединениеOpenSSL вторым параметром Новый СертификатыУдостоверяющихЦентровОС(), чтобы использовать хранилище сертификатов операционной системы, или установите корневой сертификат на сервер.HTTPСоединение для больших файлов.Content-Type.Ещё три ситуации, которые не дают текста ошибки, но ломают результат:
- 🧱 Безопасный режим. Обработка, открытая в безопасном режиме или с профилем безопасности без доступа к интернет-ресурсам, не сможет создать соединение. Разрешение выдаёт администратор в настройках профиля безопасности кластера.
- 📂 Нет прав на папку. Служба сервера 1С работает от своего пользователя Windows. Если у него нет записи в целевой каталог, файл не создастся. Пишите во временный каталог или выдайте права явно.
- 🔁 Файл занят. Повторное скачивание в тот же путь падает, если предыдущий поток не закрыт или файл открыт в Excel. Перед записью проверяйте, свободен ли файл, или пишите под новым временным именем.
Частые вопросы
Можно ли скачать файл по HTTP прямо на компьютер пользователя, минуя сервер?
HTTPСоединение работает и в клиентском коде, так что технически можно. Но в веб-клиенте объекта нет, поэтому универсальный вариант — скачать на сервере и передать через временное хранилище.Как скачать файл, который отдаётся только после POST-запроса?
Соединение.ОтправитьДляОбработки(Запрос, ИмяФайла). Тело запроса задают методом Запрос.УстановитьТелоИзСтроки(), параметр с именем файла работает так же, как у Получить().Как скачивать файлы по расписанию?
Поддерживает ли HTTPСоединение докачку прерванного файла?
Range со значением bytes=1048576-: сервер, который поддерживает частичную отдачу, вернёт код 206 и остаток файла. Склеивать части придётся самостоятельно.Можно ли получить файл по FTP тем же кодом?
FTPСоединение со своими методами, HTTPСоединение работает только по HTTP и HTTPS.