Файл лежит на сервере 1С, а нужен пользователю: рабочие способы доставки
Содержание
Чтобы передать файл с сервера на клиент в 1С 8.3, на сервере прочитайте его в ДвоичныеДанные, положите в хранилище через ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор) и верните клиенту адрес. На клиенте по этому адресу вызовите НачатьПолучениеФайлов или ПолучитьФайлССервераАсинх. В тонком и толстом клиенте можно ещё проще: взять данные из хранилища и записать их на диск методом Записать().
Типичная ситуация: регламентное задание сформировало выгрузку в D:\Exchange\out.xml на сервере 1С, а бухгалтер жмёт кнопку «Скачать» у себя на ноутбуке и получает «Файл не найден». Путь верный, только он верен для другой машины.
Почему клиент не видит файлы сервера: модель исполнения 1С
В клиент-серверном варианте код формы работает в двух местах. Процедуры с директивой &НаКлиенте выполняются на компьютере пользователя. Процедуры &НаСервере — в рабочем процессе rphost на сервере 1С. У каждого своя файловая система.
Из этого следуют три правила:
- 📁 Путь
D:\Exchange\out.xml, вычисленный на сервере, указывает на диск сервера. На клиенте он ничего не значит. - 🔒 Серверный код работает от имени пользователя службы сервера 1С (часто это
USR1CV8или доменная учётная запись службы). Права на папку нужны этой учётке, а не пользователю 1С. - 🌐 Подключённые сетевые диски вроде
Z:\службе не видны. Для сетевых папок используйте UNC-путь\\fileserver\exchange\out.xmlи выдайте доступ учётке службы.
В файловом варианте базы клиент и «сервер» работают на одной машине. Поэтому код с прямым путём при отладке на файловой копии работает, а на рабочем сервере ломается. Проверяйте выгрузку файлов на клиент-серверной копии базы, а не только на файловой.
Что можно возвращать из серверной функции на клиент:
| Тип | Можно передать | Комментарий |
|---|---|---|
ДвоичныеДанные | да | основной «контейнер» содержимого файла |
| Адрес во временном хранилище (строка) | да | данные остаются на сервере, по сети уходит только строка |
БуферДвоичныхДанных | не рассчитывайте | перед возвратом преобразуйте в ДвоичныеДанные |
Потоки (ФайловыйПоток, ПотокВПамяти) | нет | живут только там, где созданы |
Ещё один ограничитель — профили безопасности. Если для базы в кластере включён профиль безопасности, серверному коду может быть запрещён доступ к файловой системе целиком или к конкретным каталогам. Тогда ошибка возникнет уже на чтении файла на сервере, до всякой передачи.
Временное хранилище и ДвоичныеДанные: базовая схема передачи
Временное хранилище — служебная область сервера, куда можно положить значение и получить строковый адрес. Клиент по адресу забирает данные. Это стандартный и самый надёжный способ передать файл с сервера на клиент.
Порядок такой:
- На сервере создайте
Новый ДвоичныеДанные(ПолноеИмяФайла)— содержимое файла считается в память. - Поместите данные в хранилище, указав
УникальныйИдентификаторформы вторым параметром. - Верните адрес на клиент.
- На клиенте запустите получение файла по адресу.
&НаСервере
Функция АдресФайлаВыгрузки()
ИмяФайла = "D:\Exchange\out.xml";
Файл = Новый Файл(ИмяФайла);
Если Не Файл.Существует() Тогда
ВызватьИсключение "Файл выгрузки не найден на сервере: " + ИмяФайла;
КонецЕсли;
Данные = Новый ДвоичныеДанные(ИмяФайла);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Зачем второй параметр. Значение, помещённое с идентификатором формы, живёт, пока открыта форма. Без идентификатора время жизни короткое и привязано к серверному вызову. Клиент может прийти за файлом, когда данных уже нет. Если форма остаётся открытой надолго, а файлов много, освобождайте хранилище вручную: УдалитьИзВременногоХранилища(Адрес) после успешной передачи.
Вариант 1. Асинхронное получение с диалогом
Этот вариант работает во всех клиентах, включая веб-клиент, и не требует модальных окон. Пользователь увидит диалог сохранения или загрузку в браузере.
&НаКлиенте
Процедура СкачатьВыгрузку(Команда)
Адрес = АдресФайлаВыгрузки();
ПолучаемыеФайлы = Новый Массив;
ПолучаемыеФайлы.Добавить(Новый ОписаниеПередаваемогоФайла("out.xml", Адрес));
Оповещение = Новый ОписаниеОповещения("ПослеПолученияФайлов", ЭтотОбъект);
НачатьПолучениеФайлов(Оповещение, ПолучаемыеФайлы, "", Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПолученияФайлов(ПолученныеФайлы, ДополнительныеПараметры) Экспорт
Если ПолученныеФайлы = Неопределено Тогда
Возврат; // пользователь отменил сохранение
КонецЕсли;
Для Каждого Описание Из ПолученныеФайлы Цикл
Сообщить("Файл сохранён: " + Описание.Имя);
КонецЦикла;
КонецПроцедуры
В платформах с поддержкой Асинх и Ждать тот же сценарий короче: ПолучитьФайлССервераАсинх(Адрес, "out.xml") внутри асинхронной процедуры.
Старый синхронный ПолучитьФайл(Адрес, ИмяФайла, Истина) открывает модальный диалог. Если в свойствах конфигурации режим использования модальности установлен в «Не использовать», такой вызов завершится ошибкой. Новые формы пишите сразу на асинхронных методах.
Вариант 2. Тихая запись в заданный каталог
Когда диалог не нужен (обработка сама кладёт файл в C:\Обмен), в тонком и толстом клиенте данные можно забрать из хранилища и записать напрямую:
&НаКлиенте
Процедура СохранитьВКаталог(Команда)
Адрес = АдресФайлаВыгрузки();
Данные = ПолучитьИзВременногоХранилища(Адрес);
Данные.Записать("C:\Обмен\out.xml");
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
В веб-клиенте прямой доступ к диску пользователя есть только через расширение работы с файлами. Сначала проверьте его наличие НачатьПодключениеРасширенияРаботыСФайлами, при необходимости предложите установить НачатьУстановкуРасширенияРаботыСФайлами. Без расширения остаётся только вариант 1 — загрузка через браузер.
Небольшой файл можно вернуть из серверной функции прямо как ДвоичныеДанные, без хранилища. Для файлов до нескольких сотен килобайт разницы почти нет. Для крупных хранилище удобнее: данные не гоняются туда-обратно при каждом серверном вызове формы.
БуферДвоичныхДанных: когда файл нужно подготовить перед отправкой
БуферДвоичныхДанных появился в платформе 8.3 для побайтовой работы с содержимым. Сам по себе он не механизм передачи. Его используют на сервере, чтобы проверить или изменить файл, а затем переводят обратно в ДвоичныеДанные и отдают по обычной схеме.
Пример: перед отправкой пользователю проверить, что выгрузка действительно PDF, а не текст ошибки, который внешняя система записала в файл с расширением .pdf. Первые четыре байта PDF-файла — %PDF, то есть коды 37, 80, 68, 70.
&НаСервере
Функция ЭтоPDF(Данные)
Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Данные);
Если Буфер.Размер < 4 Тогда
Возврат Ложь;
КонецЕсли;
Возврат Буфер.Получить(0) = 37
И Буфер.Получить(1) = 80
И Буфер.Получить(2) = 68
И Буфер.Получить(3) = 70;
КонецФункции
Если буфер меняли, перед помещением в хранилище соберите результат: ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Буфер).
Когда буфер действительно нужен:
- 🔍 проверка сигнатуры и типа файла перед выдачей пользователю;
- ✂️ сборка файла из частей или отрезание служебного заголовка;
- 🔢 запись чисел в двоичных форматах (заголовки, контрольные поля).
Для простого «прочитать и отдать» буфер не нужен. Лишнее преобразование — лишняя копия файла в памяти рабочего процесса.
Временные файлы на сервере: формируем и сразу убираем
Часто файла на диске сервера нет. Его надо сначала создать: табличный документ в Excel, архив, XML. Делается это через временный файл.
- Получите имя:
ПолучитьИмяВременногоФайла("xlsx"). Платформа вернёт путь в каталоге временных файлов пользователя службы. - Запишите туда результат.
- Прочитайте файл в
ДвоичныеДанные. - Удалите временный файл
УдалитьФайлы(ИмяВременного). - Положите данные во временное хранилище.
&НаСервере
Функция АдресОтчетаExcel()
ТабДок = СформироватьОтчет(); // ваша функция, возвращает ТабличныйДокумент
ИмяВременного = ПолучитьИмяВременногоФайла("xlsx");
Попытка
ТабДок.Записать(ИмяВременного, ТипФайлаТабличногоДокумента.XLSX);
Данные = Новый ДвоичныеДанные(ИмяВременного);
Исключение
УдалитьФайлы(ИмяВременного);
ВызватьИсключение;
КонецПопытки;
УдалитьФайлы(ИмяВременного);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Удаление в обеих ветках — не перестраховка. Каталог временных файлов службы 1С на нагруженном сервере быстро набирает гигабайты «забытых» выгрузок, если код их не удаляет.
Не пишите временные файлы в фиксированный путь вроде C:\Temp\report.xlsx. Два пользователя, нажавшие кнопку одновременно, перезапишут файлы друг друга, и один получит чужой отчёт. ПолучитьИмяВременногоФайла даёт уникальное имя на каждый вызов.
Для архивов действует та же схема: ЗаписьZipФайла пишет во временный файл, затем он читается в ДвоичныеДанные и удаляется.
HTTP-сервис: передача файла без открытой формы
Временное хранилище привязано к сеансу и форме. Если файл нужно забирать по ссылке, из внешней программы или из другой базы 1С, удобнее HTTP-сервис. База публикуется на веб-сервере, и файл скачивается по адресу вида https://сервер/имя_публикации/hs/files/out.xml.
Порядок настройки:
- В конфигурации (или расширении) добавьте HTTP-сервис, корневой URL —
files. - Добавьте шаблон URL
/{name}и методGET. - В обработчике метода сформируйте ответ с телом из двоичных данных.
- Опубликуйте базу на веб-сервере, отметив публикацию HTTP-сервисов.
Функция ФайлGET(Запрос)
Имя = Запрос.ПараметрыURL["name"];
// Принимаем только имя файла, никаких путей
Если СтрНайти(Имя, "\") > 0 Или СтрНайти(Имя, "/") > 0 Или СтрНайти(Имя, "..") > 0 Тогда
Возврат Новый HTTPСервисОтвет(400);
КонецЕсли;
ПолноеИмя = "D:\Exchange\" + Имя;
Если Не (Новый Файл(ПолноеИмя)).Существует() Тогда
Возврат Новый HTTPСервисОтвет(404);
КонецЕсли;
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/octet-stream");
Ответ.Заголовки.Вставить("Content-Disposition", "attachment; filename=""" + Имя + """");
Ответ.УстановитьТелоИзДвоичныхДанных(Новый ДвоичныеДанные(ПолноеИмя));
Возврат Ответ;
КонецФункции
Никогда не принимайте из URL полный путь к файлу. Без проверки на .. и разделители любой, у кого есть доступ к сервису, скачает с сервера что угодно, доступное учётке веб-сервера или службы.
Забрать файл из другой базы 1С или из тонкого клиента
можно через HTTPСоединение. Второй параметр метода Получить — имя файла, куда тело ответа запишется сразу, без загрузки в переменную:
Соединение = Новый HTTPСоединение("srv1c.local", 443, "exchange", "Пароль", , 120,
Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/buh/hs/files/out.xml");
Ответ = Соединение.Получить(Запрос, "C:\Обмен\out.xml");
Если Ответ.КодСостояния <> 200 Тогда
ВызватьИсключение "Сервер вернул код " + Формат(Ответ.КодСостояния, "ЧГ=0");
КонецЕсли;
Пользователю, от имени которого идёт подключение, в 1С нужна роль с правом на этот HTTP-сервис. Заведите отдельного технического пользователя с минимальными правами.
Сравнение способов передачи
| Способ | Когда подходит | Ограничения и риски |
|---|---|---|
Хранилище + НачатьПолучениеФайлов | пользователь скачивает файл из формы, любой клиент | нужна открытая форма, диалог сохранения |
Хранилище + ДвоичныеДанные.Записать() | обработка тихо кладёт файл в известный каталог | не работает в веб-клиенте без расширения |
Возврат ДвоичныеДанные из функции | маленькие файлы, разовый вызов | весь файл идёт в ответе серверного вызова |
| HTTP-сервис | ссылка, внешние системы, другая база | нужна веб-публикация, отдельная настройка прав |
Ошибки при передаче файла и как их найти
Если ошибка возникает только у части пользователей, сравните тип клиента. Код, рассчитанный на тонкий клиент, в веб-клиенте падает на прямой записи файла.
Для разбора серверной стороны включите технологический журнал с событием EXCP. Исключения при чтении файла на сервере попадут туда с полным текстом и путём.
Большие файлы: память, скорость, нагрузка на сервер
Новый ДвоичныеДанные(Имя) и ПоместитьВоВременноеХранилище держат файл в памяти рабочего процесса. Для файла на 20 МБ это незаметно. Выгрузка на 500 МБ, которую одновременно скачивают пять человек, — это 2,5 ГБ памяти rphost только под копии, и рабочий процесс может перезапуститься с обрывом сеансов.
Что помогает:
- 📦 Сжимайте перед отправкой. XML и CSV в ZIP уменьшаются в разы, а
ЗаписьZipФайлаработает на сервере штатно. - ✂️ Делите крупные файлы на части:
РазделитьФайл(ИмяФайла, РазмерЧасти)возвращает массив имён частей. Части передаются по одной, на клиенте их собираетОбъединитьФайлы. - 🌐 Отдавайте крупные файлы HTTP-сервисом и пишите тело сразу в файл через второй параметр
HTTPСоединение.Получить. На клиенте файл не загружается в переменную целиком. - 🧹 Освобождайте хранилище сразу после передачи:
УдалитьИзВременногоХранилища(Адрес). - 🚫 Не храните файлы в реквизитах формы типа
ДвоичныеДанные. Реквизиты формы передаются при серверных вызовах, и форма начинает «тормозить» на каждой кнопке.
Для файлов, которые формируются минутами, выносите подготовку в фоновое задание. Оно кладёт результат во временное хранилище по адресу, полученному заранее в форме. Форма в это время не блокируется, а пользователь видит индикатор ожидания.