Разрабатывая выгрузку данных на удаленный сервер, программисты нередко сталкиваются с ситуацией, когда весь процесс выполняется без ошибок, но целевой файл так и не появляется в папке назначения на стороне FTP-сервера. Выясним причину этой неполадки, проанализируем типичные ошибки в коде 1С и разберем по шагам, как построить надежную и отказоустойчивую систему выгрузки файлов по протоколу FTP.
Для начала рассмотрим исходный фрагмент кода, который часто становится причиной подобного некорректного поведения системы. Проанализируем ситуацию, представленную автором темы:
Процедура ОтправитьДокументXML(Документ)
ИмяВременногоФайлаXML = ПолучитьСтрокуXML(Документ);
Если ОтправлятьНаFTP Тогда
Попытка
FTP = Новый FTPСоединение(АдресFTPСервера, ПортFTPСервера, ЛогинFTP, ПарольFTP, Неопределено, ПассивныйРежимFTPСервера, 0);
Если ЗначениеЗаполнено(ДиректорияFTPСервера) Тогда
FTP.УстановитьТекущийКаталог(ДиректорияFTPСервера);
Иначе
FTP.УстановитьТекущийКаталог("/");
КонецЕсли;
ИмяФайлаНаФТП = "";
ИмяФайлаНаФТП = "Order_" + Документ.Номер + "_" + Формат(Документ.Дата, "ДФ=dd.MM.yy") + ".xml";
FTP.Записать(ИмяВременногоФайлаXML, ИмяФайлаНаФТП);
Исключение
КонецПопытки;
КонецЕсли;
КонецПроцедуры
На первый взгляд код выглядит стандартно и работоспособно, однако в нем скрыты две критические ошибки, которые в совокупности приводят к "молчаливому" сбою работы алгоритма.
Ошибка №1: Передача содержимого файла вместо пути к нему на диске.
Обратим внимание на строку ИмяВременногоФайлаXML = ПолучитьСтрокуXML(Документ);. Функция ПолучитьСтрокуXML возвращает текстовое представление XML-документа (то есть саму XML-структуру в виде длинной строки с тегами). Однако метод Записать объекта FTPСоединение ожидает первым параметром строку, которая содержит полный путь к локальному файлу на жестком диске компьютера (например, "C:\Temp\order.xml").
Когда платформа выполняет метод FTP.Записать(ИмяВременногоФайлаXML, ИмяФайлаНаФТП), она пытается интерпретировать XML-текст как путь к файлу. Естественно, операционная система сообщает, что файла с таким именем (состоящим из XML-разметки) не существует на диске, и генерирует исключение "Файл не найден".
Ошибка №2: Пустой блок обработки исключений.
Поскольку вызов метода Записать был обернут в конструкцию Попытка ... Исключение КонецПопытки без какого-либо кода внутри блока Исключение, произошедшая ошибка была просто проигнорирована платформой. Программа не вывела никаких предупреждений пользователю, не сделала запись в журнал регистрации и продолжила свое выполнение так, словно отправка файла завершилась успешно. Это классический пример "замалчивания" ошибок, который крайне затрудняет отладку.
Чтобы исправить поведение нашей обработки и сделать выгрузку стабильной, выполним следующие шаги:
ПолучитьИмяВременногоФайла, который вернет уникальный путь в системном каталоге.ЗаписьТекста или ЗаписьXML для физического сохранения содержимого на локальный диск.Записать реальный путь к сохраненному файлу на диске.Исключение, чтобы не пропустить возможные сетевые сбои.Рассмотрим подробнее исправленный и оптимизированный вариант процедуры. Этот код спроектирован с учетом всех правил работы с файловой системой и удаленными серверами в клиент-серверном режиме:
Процедура ОтправитьДокументXML(Документ)
Если Не ОтправлятьНаFTP Тогда
Возврат;
КонецЕсли;
// 1. Получаем уникальный путь для создания временного файла на диске
ИмяВременногоФайлаXML = ПолучитьИмяВременногоФайла("xml");
// 2. Записываем текстовые данные XML во временный файл на локальном диске
Попытка
ТекстXML = ПолучитьСтрокуXML(Документ);
ЗаписьТекста = Новый ЗаписьТекста;
// Используем кодировку UTF-8 без BOM или по требованию принимающего сервера
ЗаписьТекста.Открыть(ИмяВременногоФайлаXML, КодировкаТекста.UTF8);
ЗаписьТекста.ЗаписатьСтроку(ТекстXML);
ЗаписьТекста.Закрыть();
Исключение
ВызватьИсключение "Не удалось сохранить XML-данные во временный файл на диске! Ошибка: "
+ КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
// 3. Отправка файла на FTP-сервер с обработкой ошибок
Попытка
// Указываем таймаут 30 секунд вместо 0, чтобы избежать вечного зависания процесса при сбоях сети
FTP = Новый FTPСоединение(АдресFTPСервера, ПортFTPСервера, ЛогинFTP, ПарольFTP, Неопределено, ПассивныйРежимFTPСервера, 30);
Если ЗначениеЗаполнено(ДиректорияFTPСервера) Тогда
FTP.УстановитьТекущийКаталог(ДиректорияFTPСервера);
Иначе
FTP.УстановитьТекущийКаталог("/");
КонецЕсли;
ИмяФайлаНаФТП = "Order_" + Документ.Номер + "_" + Формат(Документ.Дата, "ДФ=dd.MM.yy") + ".xml";
// Передаем ПУТЬ к локальному файлу на диске, а не сам текст XML
FTP.Записать(ИмяВременногоФайлаXML, ИмяФайлаНаФТП);
Сообщить("Файл успешно отправлен на FTP: " + ИмяФайлаНаФТП);
Исключение
// Логируем ошибку в системный Журнал регистрации для системного администратора
ЗаписьЖурналаРегистрации("Выгрузка на FTP",
УровеньЖурналаРегистрации.Ошибка,
Документ.Метаданные(),
Документ.Ссылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
// Выводим пользователю понятное сообщение об ошибке
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Ошибка при отправке файла на FTP: "
+ КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
// 4. Обязательное удаление временного файла после отправки (в блоке очистки ресурсов)
Попытка
ФайлЛокальный = Новый Файл(ИмяВременногоФайлаXML);
Если ФайлЛокальный.Существует() Тогда
УдалитьФайлы(ИмяВременногоФайлаXML);
КонецЕсли;
Исключение
// Ошибку удаления временного файла фиксируем в фоне, чтобы не прерывать основной процесс
ЗаписьЖурналаРегистрации("Очистка временных файлов",
УровеньЖурналаРегистрации.Предупреждение,
,
,
"Не удалось удалить временный файл " + ИмяВременногоФайлаXML + ". " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Для создания действительно отказоустойчивых интеграционных механизмов необходимо учитывать важные технические особенности функционирования объекта FTPСоединение в платформе "1С:Предприятие":
Пассивный режим (Passive Mode)
Обязательно используйте пассивный режим работы FTP-сервера. Большинство современных корпоративных сетей защищены сетевыми экранами (Firewall) и используют механизмы трансляции сетевых адресов (NAT). В таких условиях активный режим передачи данных работать не будет: соединение установится, но при попытке получить список файлов или передать файл программа зависнет или завершится аварийно. Для активации пассивного режима передавайте значение Истина в шестой параметр конструктора FTPСоединение.
Ограничение времени ожидания (Таймаут)
Никогда не оставляйте таймаут равным нулю (без ограничений), особенно если выгрузка выполняется в фоновом режиме на сервере. Если удаленный сервер внезапно перестанет отвечать посреди сессии передачи данных, фоновое задание в 1С зависнет навсегда. Это может привести к исчерпанию ресурсов сервера 1С и блокировке базы данных. Рекомендуется устанавливать время ожидания в пределах от 15 до 60 секунд.
Различия между FTPS и SFTP
Важно понимать разницу между защищенными протоколами передачи данных. Объект FTPСоединение в платформе 1С поддерживает стандартный протокол FTP и протокол FTPS (FTP поверх SSL/TLS). Для активации шифрования необходимо передать объект ЗащищенноеСоединениеOpenSSL в качестве параметра конструктора.
Однако объект 1С не поддерживает работу по протоколу SFTP (SSH File Transfer Protocol, работающий через порт 22). Если ваш партнер требует использовать именно SFTP, вам придется использовать внешние консольные утилиты (например, WinSCP или curl) и запускать их через метод ЗапуститьПриложение().
Права доступа на сервере 1С
Если данный код выполняется на стороне сервера 1С (в клиент-серверном варианте работы), убедитесь, что у учетной записи службы сервера 1С (например, USR1CV8) есть полные права на чтение и запись в папку с временными файлами. Использование метода ПолучитьИмяВременногоФайла() гарантирует, что файл будет создан в разрешенной системной директории, что исключает ошибки доступа к файловой системе.