Разработка под Web-клиент в среде 1С:Предприятие всегда накладывает на программиста ряд ограничений, связанных с безопасностью веб-браузеров. Одной из классических задач, вызывающих трудности у разработчиков, является интерактивный выбор файла на компьютере пользователя и последующая его отправка на внешний FTP-сервер. В то время как на Тонком клиенте этот код пишется буквально в несколько строк и работает напрямую с файловой системой, в Web-клиенте мы сталкиваемся с блокировкой модальных окон и невозможностью прямого доступа к локальным дискам.
В этой статье мы подробно разберем, почему возникают эти сложности, проанализируем архитектуру безопасной работы с файлами в веб-среде и реализуем готовое решение, которое будет работать на платформе 1С:Предприятие 8.3 начиная с версии 8.3.15 без необходимости установки расширения работы с файлами.
Попытка вызвать привычный метод выбора файлов в Web-клиенте часто приводит к ошибке «Использование модальных окон в данном режиме запрещено». Выясним причину этого поведения. Современные веб-браузеры в целях безопасности полностью блокируют синхронные модальные вызовы, которые останавливают выполнение кода страницы до тех пор, пока пользователь не закроет диалоговое окно. Если в свойствах вашей конфигурации 1С для свойства Режим использования модальности установлено значение Не использовать, то вызовы традиционных синхронных методов объекта ДиалогВыбораФайла приведут к аварийному завершению работы.
Вторая проблема заключается в архитектуре FTP-соединения. Объект платформы FTPСоединение физически инициализируется и выполняет отправку данных только на стороне сервера (так как веб-клиент в браузере не имеет возможности устанавливать прямые TCP-соединения с произвольными портами из-за ограничений политики безопасности CORS). Следовательно, файл должен быть сначала передан с клиентского компьютера на сервер 1С.
Многие разработчики совершают ошибку, пытаясь передать адрес временного хранилища (вид навигационной ссылки e1cib/tempstorage/...) напрямую в методы объекта FTPСоединение. Это вызывает ошибку «Файл не найден», поскольку служба FTP-клиента на сервере ожидает получить реальный физический путь к файлу в операционной системе (например, C:\Windows\Temp\tempfile.bin или /tmp/tempfile.bin для Linux), а не внутреннюю ссылку СУБД 1С.
Для реализации корректной и стабильной загрузки файла на FTP-сервер мы должны выстроить цепочку событий следующим образом:
ПолучитьИзВременногоХранилища().ПолучитьИмяВременногоФайла(). Это необходимо, так как FTPСоединение работает только с физическими путями.Рассмотрим подробнее программную реализацию этой схемы.
Начиная с версии платформы 1С 8.3.15, разработчикам доступен удобный асинхронный метод НачатьПомещениеФайлаНаСервер(), который не требует предварительной установки расширения работы с файлами в браузере. Настроим параметры диалога с помощью объекта ПараметрыДиалогаПомещенияФайлов и запустим процесс выбора.
Создадим процедуру на клиенте, которая будет привязана к кнопке выбора файла на форме:
&НаКлиенте
Процедура ВыбратьИОтправитьНаFTP(Команда)
// Настраиваем параметры диалога выбора файлов в немодальном режиме
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.Заголовок = "Выберите файл для отправки на FTP-сервер";
ПараметрыДиалога.МножественныйВыбор = Ложь;
ПараметрыДиалога.Фильтр = "Все файлы (*.*)|*.*";
// Описываем процедуру обратного вызова, которая сработает после выбора файла пользователем
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораИПомещенияФайла", ЭтотОбъект);
// Запускаем асинхронный импорт. Файл будет автоматически загружен во временное хранилище
НачатьПомещениеФайлаНаСервер(ОписаниеОповещения, , , , ПараметрыДиалога, УникальныйИдентификатор);
КонецПроцедуры
Теперь проанализируем процедуру обратного вызова ПослеВыбораИПомещенияФайла. Она принимает два параметра, из которых мы получаем всю необходимую информацию о загруженном файле.
&НаКлиенте
Процедура ПослеВыбораИПомещенияФайла(ОписаниеПомещенногоФайла, ДополнительныеПараметры) Экспорт
// Если пользователь закрыл диалоговое окно браузера или отменил выбор
Если ОписаниеПомещенногоФайла = Неопределено Тогда
Возврат;
КонецЕсли;
// Получаем адрес временного хранилища на сервере и оригинальное имя файла
АдресХранилища = ОписаниеПомещенногоФайла.Адрес;
ИмяФайла = ОписаниеПомещенногоФайла.СсылкаНаФайл.Имя;
// Передаем управление на сервер для выполнения отправки на FTP
Попытка
ОтправитьФайлНаFTPНаСервере(АдресХранилища, ИмяФайла);
ПоказатьПредупреждение(, "Файл успешно передан на FTP-сервер!");
Исключение
ПоказатьПредупреждение(, "Произошла ошибка при отправке файла: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Перейдем к серверной логике. Нам необходимо выполнить процедуру с директивой компиляции &НаСервереБезКонтекста. Это позволит оптимизировать производительность и исключит передачу лишнего контекста формы между клиентом и сервером. В этой процедуре мы извлечем объект ДвоичныеДанные, временно запишем его на жесткий диск сервера 1С, установим соединение с удаленным сервером через объект FTPСоединение и отправим данные.
Обратите внимание на блок обработки исключений: мы обязаны гарантировать удаление временного файла с диска сервера 1С при любом исходе операции (успех или ошибка сети), чтобы избежать утечки дисковой памяти.
&НаСервереБезКонтекста
Процедура ОтправитьФайлНаFTPНаСервере(Знач АдресХранилища, Знач ИмяФайла)
// 1. Получаем двоичные данные файла из временного хранилища по переданному адресу
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресХранилища);
// 2. Определяем расширение оригинального файла для корректного создания временного файла
РасширениеФайла = "";
ПозицияТочки = Найти(ИмяФайла, ".");
Если ПозицияТочки > 0 Тогда
РасширениеФайла = Сред(ИмяФайла, ПозицияТочки);
КонецЕсли;
// 3. Создаем временный файл на физическом диске сервера 1С
ИмяВременногоФайла = ПолучитьИмяВременногоФайла(РасширениеФайла);
ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
// 4. Подключаемся к FTP-серверу и производим запись
Попытка
// Настоятельно рекомендуется использовать пассивный режим работы (Параметр "Истина")
// для стабильного обхода сетевых экранов и брандмауэров сервера
FTPСоединение = Новый FTPСоединение("ftp.yoursite.com", 21, "your_login", "your_password", , Истина, 60);
// Передаем физический путь временного файла на сервере и желаемое имя файла на FTP-сервере
FTPСоединение.Записать(ИмяВременногоФайла, ИмяФайла);
Исключение
// В случае сбоя сети или авторизации обязательно подчищаем созданный временный файл на сервере
Попытка
УдалитьФайлы(ИмяВременногоФайла);
Исключение
// Подавляем ошибку удаления временного файла, чтобы выдать основную ошибку подключения
КонецПопытки;
ВызватьИсключение "Не удалось отправить файл по FTP. Описание ошибки: " + ОписаниеОшибки();
КонецПопытки;
// 5. Очищаем ресурсы после успешной отправки файла
Попытка
УдалитьФайлы(ИмяВременногоФайла);
Исключение
// Возможные ошибки удаления пишем в Журнал Регистрации или игнорируем
КонецПопытки;
КонецПроцедуры
При настройке и отладке данного механизма рекомендуем обратить внимание на следующие технические нюансы:
FTPСоединение в значение Истина. В противном случае брандмауэры на стороне сервера 1С или на стороне FTP-сервера заблокируют передачу данных, так как активный режим требует открытия сервером входящих портов.НачатьПомещениеФайлаНаСервер() обязательно передавайте в качестве последнего параметра УникальныйИдентификатор текущей формы. Это привяжет время жизни файла во временном хранилище к сеансу работы данной формы, и платформа автоматически очистит временное хранилище СУБД после закрытия формы пользователем.usr1cv8), есть права на запись и чтение в системной временной папке сервера, откуда метод ПолучитьИмяВременногоФайла() берет пути.Таким образом, мы успешно обошли ограничения безопасности Web-клиента, избавились от ошибок несовместимости модального режима работы интерфейса и реализовали надежную и отказоустойчивую схему передачи файлов на внешние ресурсы через сервер 1С.