При разработке функционала по загрузке файлов в систему 1С программисты часто сталкиваются с ситуацией, когда код идеально работает в тонком клиенте, но при запуске через веб-клиент (в браузере) файлы просто не копируются, при этом система не выдает никаких видимых ошибок. Рассмотрим подробнее, почему так происходит, и разберем правильную архитектуру процесса передачи файлов в современных версиях платформы 1С:Предприятие 8.3.
Проанализируем типичный сценарий: программист пытается поместить файл во временное хранилище в обработчике ПередЗаписью, чтобы при сохранении документа файл сразу «улетел» на сервер. В веб-клиенте это часто приводит к фиаско. Причина кроется в особенностях работы браузеров. Событие ПередЗаписью инициирует транзакцию записи на стороне сервера. Методы семейства НачатьПомещение... являются асинхронными (используют callback-модель). Браузер, находясь в процессе отправки данных формы, может блокировать или игнорировать параллельные асинхронные запросы к файловой системе, которые не были инициированы явным действием пользователя (например, нажатием кнопки).
В тонком клиенте 1С имеет больше контроля над потоками выполнения, а в веб-клиенте мы жестко ограничены событийным циклом (Event Loop) браузера. Выясним причину: если асинхронная операция загрузки файла не завершится до того, как форма начнет закрываться или переходить в режим чтения после записи, соединение может быть разорвано.
Посмотрим на пример кода, который вызывал затруднения. Здесь используется устаревающий метод НачатьПомещениеФайловНаСервер внутри процедуры ПередЗаписью:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
МассивПолучаемыхФайлов = Новый Массив;
Для Каждого ТекущаяСтрока Из ЭтотОбъект.Объект.СписокФайлов Цикл
Если ТекущаяСтрока.ПризнакРедактирования = Истина Тогда
МассивПолучаемыхФайлов.Добавить(Новый ОписаниеПередаваемогоФайла(ТекущаяСтрока.Файл));
КонецЕсли;
КонецЦикла;
ОписаниеОповещения = Новый ОписаниеОповещения("ЗагрузитьЗавершение", ЭтотОбъект);
Идентификатор = ЭтотОбъект.УникальныйИдентификатор;
// В веб-клиенте этот вызов может не отработать корректно внутри системного события
НачатьПомещениеФайловНаСервер(ОписаниеОповещения, , , МассивПолучаемыхФайлов, Идентификатор);
КонецПроцедуры
Самый надежный способ исправить ситуацию в веб-клиенте — разделить процесс записи документа и процесс загрузки файлов. Разберем по шагам, как это реализовать:
Рассмотрим пример реализации через кнопку, который гарантированно работает в веб-клиенте:
&НаКлиенте
Процедура КомандаЗагрузитьФайл(Команда)
// Сначала убеждаемся, что расширение для работы с файлами подключено
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеПодключенияРасширения", ЭтотОбъект);
НачатьПодключениеРасширенияРаботыСФайлами(ОписаниеОповещения);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПодключенияРасширения(Подключено, ДополнительныеПараметры) Экспорт
Если Подключено Тогда
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
Диалог.Показать(ОписаниеОповещения);
Иначе
Сообщить("Для загрузки файлов в веб-клиенте необходимо установить расширение браузера.");
КонецЕсли;
КонецПроцедуры
Начиная с версии платформы 8.3.15, рекомендуется отказываться от ОписаниеОповещения в пользу методов с постфиксом Асинх. Это делает код более читаемым и стабильным. Проанализируем, как переписать загрузку файлов на новый лад. Использование Ждать (await) позволяет писать асинхронный код так, будто он синхронный.
&НаКлиенте
Асинх Процедура ПрикрепитьФайлыАсинх()
// Проверяем расширение
Установлено = Ждать ПодключитьРасширениеРаботыСФайламиАсинх();
Если Не Установлено Тогда
Ждать УстановитьРасширениеРаботыСФайламиАсинх();
КонецЕсли;
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ВыбранныеФайлы = Ждать Диалог.ВыбратьАсинх();
Если ВыбранныеФайлы <> Неопределено Тогда
Для Каждого ИмяФайла Из ВыбранныеФайлы Цикл
// Помещаем файл на сервер и получаем результат
Результат = Ждать ПоместитьФайлНаСерверАсинх(,,, ИмяФайла, УникальныйИдентификатор);
Если Результат <> Неопределено Тогда
// Вызываем серверный метод для сохранения из временного хранилища в каталог
СохранитьФайлНаДискеНаСервере(Результат.Адрес, Результат.СсылкаНаФайл.Имя);
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
После того как файл попал во временное хранилище (объект АдресХранилища), его необходимо физически записать на диск сервера. Важно помнить, что в веб-клиенте серверный код выполняется от имени пользователя, под которым запущен веб-сервер (IIS или Apache), поэтому у него должны быть права на запись в целевой каталог.
&НаСервере
Процедура СохранитьФайлНаДискеНаСервере(АдресХранилища, ИмяФайла)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ДвоичныеДанные <> Неопределено Тогда
// Путь должен быть доступен пользователю веб-сервера!
ПутьККаталогу = "C:\FilesStorage\";
СоздатьКаталог(ПутьККаталогу);
ПолныйПуть = ПутьККаталогу + ИмяФайла;
Попытка
ДвоичныеДанные.Записать(ПолныйПуть);
УдалитьИзВременногоХранилища(АдресХранилища);
Исключение
ЗаписьЖурналаРегистрации("ЗагрузкаФайла", УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
ВызватьИсключение "Ошибка при сохранении файла на сервере: " + ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Проанализируем дополнительные факторы, которые могут влиять на результат:
УникальныйИдентификатор формы. Это гарантирует, что временное хранилище не будет очищено преждевременно, пока форма открыта.maxAllowedContentLength). Если файл большой, асинхронная операция может завершиться ошибкой 404.13 или 413 (Payload Too Large).ДиалогВыбораФайла и методов прямого доступа к файловой системе. Всегда проверяйте его наличие через ПодключитьРасширениеРаботыСФайламиАсинх().Таким образом, мы выяснили, что основная проблема заключалась в неудачном выборе события для инициации загрузки. Переход на явное управление процессом через кнопки и использование современного асинхронного стека Асинх/Ждать позволяет создавать надежные решения для работы с файлами в любых клиентах 1С.