Файл лежит на сервере 1С, а нужен пользователю: рабочие способы доставки

· 10 мин

Содержание

Чтобы передать файл с сервера на клиент в 1С 8.3, на сервере прочитайте его в ДвоичныеДанные, положите в хранилище через ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор) и верните клиенту адрес. На клиенте по этому адресу вызовите НачатьПолучениеФайлов или ПолучитьФайлССервераАсинх. В тонком и толстом клиенте можно ещё проще: взять данные из хранилища и записать их на диск методом Записать().

Типичная ситуация: регламентное задание сформировало выгрузку в D:\Exchange\out.xml на сервере 1С, а бухгалтер жмёт кнопку «Скачать» у себя на ноутбуке и получает «Файл не найден». Путь верный, только он верен для другой машины.

Почему клиент не видит файлы сервера: модель исполнения 1С

В клиент-серверном варианте код формы работает в двух местах. Процедуры с директивой &НаКлиенте выполняются на компьютере пользователя. Процедуры &НаСервере — в рабочем процессе rphost на сервере 1С. У каждого своя файловая система.

Из этого следуют три правила:

  • 📁 Путь D:\Exchange\out.xml, вычисленный на сервере, указывает на диск сервера. На клиенте он ничего не значит.
  • 🔒 Серверный код работает от имени пользователя службы сервера 1С (часто это USR1CV8 или доменная учётная запись службы). Права на папку нужны этой учётке, а не пользователю 1С.
  • 🌐 Подключённые сетевые диски вроде Z:\ службе не видны. Для сетевых папок используйте UNC-путь \\fileserver\exchange\out.xml и выдайте доступ учётке службы.

В файловом варианте базы клиент и «сервер» работают на одной машине. Поэтому код с прямым путём при отладке на файловой копии работает, а на рабочем сервере ломается. Проверяйте выгрузку файлов на клиент-серверной копии базы, а не только на файловой.

Что можно возвращать из серверной функции на клиент:

ТипМожно передатьКомментарий
ДвоичныеДанныедаосновной «контейнер» содержимого файла
Адрес во временном хранилище (строка)даданные остаются на сервере, по сети уходит только строка
БуферДвоичныхДанныхне рассчитывайтеперед возвратом преобразуйте в ДвоичныеДанные
Потоки (ФайловыйПоток, ПотокВПамяти)нетживут только там, где созданы

Ещё один ограничитель — профили безопасности. Если для базы в кластере включён профиль безопасности, серверному коду может быть запрещён доступ к файловой системе целиком или к конкретным каталогам. Тогда ошибка возникнет уже на чтении файла на сервере, до всякой передачи.

Временное хранилище и ДвоичныеДанные: базовая схема передачи

Временное хранилище — служебная область сервера, куда можно положить значение и получить строковый адрес. Клиент по адресу забирает данные. Это стандартный и самый надёжный способ передать файл с сервера на клиент.

Порядок такой:

  1. На сервере создайте Новый ДвоичныеДанные(ПолноеИмяФайла) — содержимое файла считается в память.
  2. Поместите данные в хранилище, указав УникальныйИдентификатор формы вторым параметром.
  3. Верните адрес на клиент.
  4. На клиенте запустите получение файла по адресу.
&НаСервере
Функция АдресФайлаВыгрузки()

	ИмяФайла = "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. Делается это через временный файл.

  1. Получите имя: ПолучитьИмяВременногоФайла("xlsx"). Платформа вернёт путь в каталоге временных файлов пользователя службы.
  2. Запишите туда результат.
  3. Прочитайте файл в ДвоичныеДанные.
  4. Удалите временный файл УдалитьФайлы(ИмяВременного).
  5. Положите данные во временное хранилище.
&НаСервере
Функция АдресОтчетаExcel()

	ТабДок = СформироватьОтчет(); // ваша функция, возвращает ТабличныйДокумент

	ИмяВременного = ПолучитьИмяВременногоФайла("xlsx");
	Попытка
		ТабДок.Записать(ИмяВременного, ТипФайлаТабличногоДокумента.XLSX);
		Данные = Новый ДвоичныеДанные(ИмяВременного);
	Исключение
		УдалитьФайлы(ИмяВременного);
		ВызватьИсключение;
	КонецПопытки;
	УдалитьФайлы(ИмяВременного);

	Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);

КонецФункции

Удаление в обеих ветках — не перестраховка. Каталог временных файлов службы 1С на нагруженном сервере быстро набирает гигабайты «забытых» выгрузок, если код их не удаляет.

Не пишите временные файлы в фиксированный путь вроде C:\Temp\report.xlsx. Два пользователя, нажавшие кнопку одновременно, перезапишут файлы друг друга, и один получит чужой отчёт. ПолучитьИмяВременногоФайла даёт уникальное имя на каждый вызов.

Для архивов действует та же схема: ЗаписьZipФайла пишет во временный файл, затем он читается в ДвоичныеДанные и удаляется.

HTTP-сервис: передача файла без открытой формы

Временное хранилище привязано к сеансу и форме. Если файл нужно забирать по ссылке, из внешней программы или из другой базы 1С, удобнее HTTP-сервис. База публикуется на веб-сервере, и файл скачивается по адресу вида https://сервер/имя_публикации/hs/files/out.xml.

Порядок настройки:

  1. В конфигурации (или расширении) добавьте HTTP-сервис, корневой URL — files.
  2. Добавьте шаблон URL /{name} и метод GET.
  3. В обработчике метода сформируйте ответ с телом из двоичных данных.
  4. Опубликуйте базу на веб-сервере, отметив публикацию 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Соединение.Получить. На клиенте файл не загружается в переменную целиком.
  • 🧹 Освобождайте хранилище сразу после передачи: УдалитьИзВременногоХранилища(Адрес).
  • 🚫 Не храните файлы в реквизитах формы типа ДвоичныеДанные. Реквизиты формы передаются при серверных вызовах, и форма начинает «тормозить» на каждой кнопке.

Для файлов, которые формируются минутами, выносите подготовку в фоновое задание. Оно кладёт результат во временное хранилище по адресу, полученному заранее в форме. Форма в это время не блокируется, а пользователь видит индикатор ожидания.

Можно ли передать файл с сервера на клиент без временного хранилища?
Да. Серверная функция может вернуть ДвоичныеДанные напрямую. Для небольших файлов это нормально, для крупных хранилище экономнее.
Где на сервере лежат файлы, созданные ПолучитьИмяВременногоФайла?
В каталоге временных файлов пользователя, от имени которого запущена служба сервера 1С. Точный путь вернёт сама функция — выведите его в журнал регистрации при отладке.
Почему в файловой базе всё работает, а в клиент-серверной нет?
В файловом варианте серверный код выполняется на компьютере пользователя и видит его диски. В клиент-серверном — на сервере, от имени службы, со своими дисками и правами.
Как передать несколько файлов за один раз?
Поместите каждый файл в хранилище и добавьте в массив по ОписаниюПередаваемогоФайла. НачатьПолучениеФайлов примет весь массив. Либо упакуйте файлы в один ZIP на сервере.
Работает ли ПолучитьИзВременногоХранилища на клиенте?
Да, метод доступен на клиенте. Записать полученные данные на диск по пути можно в тонком и толстом клиенте, в веб-клиенте — только с расширением работы с файлами.