При разработке подсистем хранения файлов часто возникает задача передачи файлов (сканов документов, изображений, PDF) из базы данных на клиентскую сторону. У разработчиков нередко возникает вопрос: можно ли разгрузить сервер 1С, если передать двоичные данные напрямую на клиент и только там вызвать метод ПоместитьВоВременноеХранилище()? Разберем архитектурные особенности клиент-серверного взаимодействия, выясним, где на самом деле располагается временное хранилище, и проанализируем оптимальные способы передачи файлов.
Рассмотрим механику работы функции ПоместитьВоВременноеХранилище(). Независимо от того, в каком контексте она вызвана — на сервере или на тонком клиенте, — само временное хранилище всегда физически создается и располагается на стороне сервера (в оперативной памяти рабочего процесса rphost).
Проанализируем цепочку вызовов, если разработчик получает двоичные данные на сервере, возвращает их на клиент и вызывает ПоместитьВоВременноеХранилище() на клиенте:
ХранилищеЗначения).ПоместитьВоВременноеХранилище() на клиенте файл целиком отправляется по сети обратно на сервер, где и помещается в память.Таким образом, попытка поместить данные во временное хранилище на стороне клиента приводит к трехкратной прокачке тяжелого файла по сети, создавая колоссальную нагрузку на сетевой канал и рабочий процесс сервера.
Разберем классический и наиболее надежный подход. На стороне сервера мы считываем данные из регистра, сразу же помещаем их во временное хранилище и возвращаем на клиент только компактную строковую ссылку (адрес вида e1cib/tempstorage/...).
Посмотрим на пример серверной функции, извлекающей файл из регистра сведений:
&НаСервере
Функция ПолучитьАдресФайлаВоВременномХранилище(ВладелецФайла, ИмяФайла)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ХранилищеФайлов.ДвоичныеДанныеФайла КАК ДвоичныеДанныеФайла
|ИЗ
| РегистрСведений.ХранилищеФайлов КАК ХранилищеФайлов
|ГДЕ
| ХранилищеФайлов.ВладелецФайла = &ВладелецФайла
| И ХранилищеФайлов.ИмяФайла = &ИмяФайла";
Запрос.УстановитьПараметр("ВладелецФайла", ВладелецФайла);
Запрос.УстановитьПараметр("ИмяФайла", ИмяФайла);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() И ЗначениеЗаполнено(Выборка.ДвоичныеДанныеФайла) Тогда
// Извлекаем двоичные данные из ХранилищаЗначения
ДвоичныеДанные = Выборка.ДвоичныеДанныеФайла.Получить();
// Помещаем сразу во временное хранилище формы и возвращаем адрес
Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
КонецЕсли;
Возврат "";
КонецФункции
Обратите внимание на важные детали в реализации этого кода:
ПолучитьИмяВременногоФайла() и ДвоичныеДанные.Записать(). Метод ПоместитьВоВременноеХранилище() напрямую принимает объект ДвоичныеДанные, извлеченный из ХранилищеЗначения.УникальныйИдентификатор управляемой формы, мы привязываем время жизни данных в хранилище к жизненному циклу формы. При закрытии окна память на сервере очистится автоматически.Теперь рассмотрим код на клиенте, который принимает полученный адрес и отдает файл пользователю:
&НаКлиенте
Процедура СкачатьФайл(Команда)
ТекущаяСтрока = Элементы.ФТЧДокументыДоговора.ТекущиеДанные;
Если ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
АдресВХ = ПолучитьАдресФайлаВоВременномХранилище(Объект.Ссылка, ТекущаяСтрока.ИмяФайла);
Если ЗначениеЗаполнено(АдресВХ) Тогда
// Получаем файл на стороне клиента
ПолучитьФайл(АдресВХ, ТекущаяСтрока.ИмяФайла, Истина);
// Освобождаем оперативную память сервера немедленно
Если ЭтоАдресВременногоХранилища(АдресВХ) Тогда
УдалитьИзВременногоХранилища(АдресВХ);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Если пользователи работают со сканами большого размера, ручное удаление адреса через УдалитьИзВременногоХранилища() сразу после сохранения файла позволяет не ждать закрытия формы и моментально освобождает оперативную память сервера.
В платформе 1С существует альтернативный механизм, позволяющий вообще отказаться от ручного формирования временного хранилища — использование глобальной функции ПолучитьНавигационнуюСсылку().
Посмотрим на пример формирования ссылки через ключ записи регистра сведений:
&НаКлиенте
Процедура ПолучитьФайлПоНавигационнойСсылке()
ТекущаяСтрока = Элементы.ФТЧДокументыДоговора.ТекущиеДанные;
// Формируем отбор по всем измерениям регистра
Отбор = Новый Структура;
Отбор.Вставить("ВладелецФайла", Объект.Ссылка);
Отбор.Вставить("ИмяФайла", СокрЛП(ТекущаяСтрока.ИмяФайла));
КлючЗаписиРег = РегистрыСведений.ХранилищеФайлов.СоздатьКлючЗаписи(Отбор);
// Получаем прямую навигационную ссылку к ресурсу типа ХранилищеЗначения
НавигационнаяСсылка = ПолучитьНавигационнуюСсылку(КлючЗаписиРег, "ДвоичныеДанныеФайла");
ПолучитьФайл(НавигационнаяСсылка, ТекущаяСтрока.ИмяФайла, Истина);
КонецПроцедуры
Разберем типичные проблемы, которые возникают при использовании навигационных ссылок и временного хранилища:
Возникает при неполном составе структуры для СоздатьКлючЗаписи(). Чтобы платформа смогла найти ресурс, структура отбора обязана содержать значения всех без исключения измерений регистра. Если регистр периодический, обязательным параметром является Период. Кроме того, если измерение имеет тип строки фиксированной длины или содержит неэкранированные спецсимволы, URL навигационной ссылки может сформироваться с ошибкой.
Навигационная ссылка к реквизиту объекта или ресурсу регистра начинается с e1cib/data/.... Это не временное хранилище, а прямая ссылка на данные в информационной базе. Передача такой строки в метод УдалитьИзВременногоХранилища() вызывает исключение «Ошибка удаления значения из временного хранилища». Во временном хранилище адреса всегда имеют префикс e1cib/tempstorage/.... Чтобы избежать сбоев, всегда используйте проверку ЭтоАдресВременногоХранилища(Адрес) перед удалением.
При использовании навигационной ссылки платформа считывает данные напрямую из базы под правами текущего сеанса. Если у пользователя настроен RLS на регистр, чтение завершится ошибкой доступа. Серверная функция с помещением во временное хранилище позволяет выполнить чтение в привилегированном режиме, изолируя клиент от тонкостей настройки прав.
Таким образом, для передачи тяжелых бинарных данных наиболее сбалансированным подходом является чтение данных на сервере с незамедлительным помещением во временное хранилище и последующей очисткой адреса на клиенте сразу после выгрузки файла пользователю.