Как переместить файлы на FTP с перезаписью в 1С и исправить ошибку Quote command returned error

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При организации интеграционных процессов и обмена данными между 1С и внешними системами часто используется протокол FTP. Одной из типичных задач является перенос обработанных файлов (например, файлов заказов, накладных или каталогов товаров в формате XML) из одной папки на сервере в другую — например, из папки «Входящие» в архивную директорию. Однако при повторном выполнении этой операции разработчики регулярно сталкиваются с неприятной ошибкой: «Ошибка работы с Интернет: ftp://мойадрес Quote command returned error».

В этой статье мы подробно разберем, почему возникает данная проблема, проанализируем подводные камни стандартных методов платформы 1С и рассмотрим несколько надежных способов перемещения файлов с автоматической перезаписью, которые предотвратят потерю важных данных.

Выясняем причину: почему стандартное перемещение выдает ошибку?

Рассмотрим исходный программный код, который часто используют для решения задачи перемещения:


СоединениеFTP = ПодключитьсяКFTPСерверу();
СоединениеFTP.УстановитьТекущийКаталог("/otkuda");
ФайлыXML = СоединениеFTP.НайтиФайлы(СоединениеFTP.ТекущийКаталог(), "*.xml");
    
Для Каждого Файл Из ФайлыXML Цикл
    СоединениеFTP.Переместить("/otkuda/" + Файл.Имя, "/kuda/" + Файл.Имя);
КонецЦикла;

В первый раз этот код отрабатывает безупречно. Но при повторном запуске, когда файлы с такими же именами уже присутствуют в целевом каталоге /kuda/, система аварийно завершает работу.

Дело в том, что метод Переместить() объекта FTPСоединение на уровне сетевых запросов транслируется в стандартные команды протокола FTP: RNFR (Rename From — переименовать из) и RNTO (Rename To — переименовать в). То есть физически файл не скачивается и не загружается заново, сервер просто меняет его путь (переименовывает).

Согласно спецификации протокола FTP (RFC 959), если клиент пытается переименовать файл, указав имя уже существующего в целевой папке файла, поведение сервера зависит от его внутренней конфигурации и операционной системы:

  1. Большинство популярных FTP-серверов (таких как FileZilla Server, vsftpd, ProFTPD) из соображений безопасности заблокируют эту операцию и вернут код ошибки 550 File already exists.
  2. Платформа 1С трактует любой ответ сервера об ошибке при выполнении низкоуровневых команд как исключение «Quote command returned error».

Анализ опасного решения: почему нельзя просто использовать Попытка...Исключение

В процессе обсуждения проблемы на форумах часто предлагают «быстрое» решение — обернуть перемещение в конструкцию Попытка...Исключение и в случае ошибки удалять файл из источника:


Для Каждого Файл Из ФайлыXML Цикл
    Попытка         
        СоединениеFTP.Переместить("/otkuda/" + Файл.Имя, "/kuda/" + Файл.Имя);
    Исключение
        СоединениеFTP.Удалить("/otkuda/", Файл.Имя); // КРАЙНЕ ОПАСНО!
    КонецПопытки;
КонецЦикла;

Внимание! Это критически опасный код, который приведет к безвозвратной потере данных! Проанализируем, что произойдет при его выполнении:

  1. Новый файл с актуальными данными появляется в папке /otkuda/.
  2. В папке /kuda/ лежит старая, уже обработанная версия файла с таким же именем.
  3. Метод Переместить() завершается ошибкой, так как файл в приемнике уже есть.
  4. Срабатывает блок Исключение, в котором программа просто удаляет новый (!) файл из папки /otkuda/.
  5. В итоге: новые данные стерты, а в архиве осталась лежать старая копия файла. Интеграция нарушена, данные потеряны.

Теперь рассмотрим правильные, безопасные и эффективные способы решения этой задачи.

Способ 1. Предварительное удаление файла в каталоге-приемнике (Рекомендуемый)

Самый надежный способ при работе через объект FTPСоединение — перед вызовом метода Переместить() проверить, существует ли файл в целевом каталоге. Если он там есть, его необходимо предварительно удалить средствами 1С, и только после этого выполнять перемещение. Это гарантирует, что актуальный файл из источника займет свое место в приемнике.

Реализуем этот алгоритм на практике:


СоединениеFTP = ПодключитьсяКFTPСерверу();

// Получаем список файлов в исходном каталоге
ФайлыXML = СоединениеFTP.НайтиФайлы("/otkuda", "*.xml");

Для Каждого Файл Из ФайлыXML Цикл
    
    ИмяФайла = Файл.Имя;
    ПутьИсточник = "/otkuda/" + ИмяФайла;
    ПутьПриемник = "/kuda/" + ИмяФайла;
    
    // Проверяем наличие точно такого же файла в каталоге-получателе
    ФайлыВПриемнике = СоединениеFTP.НайтиФайлы("/kuda", ИмяФайла);
    
    Если ФайлыВПриемнике.Количество() > 0 Тогда
        Попытка
            // Удаляем старый файл в приемнике, чтобы освободить место
            СоединениеFTP.Удалить("/kuda", ИмяФайла);
        Исключение
            ЗаписьЖурналаРегистрации("FTP.ОшибкаУдаления",
                УровеньЖурналаРегистрации.Предупреждение, , ,
                "Не удалось удалить старый файл в приемнике: " + ИмяФайла + ". Ошибка: " + ОписаниеОшибки());
            Продолжить; // Переходим к следующему файлу, этот переместить не сможем
        КонецПопытки;
    КонецЕсли;
    
    // Теперь перемещение гарантированно выполнится без ошибок перезаписи
    Попытка
        СоединениеFTP.Переместить(ПутьИсточник, ПутьПриемник);
    Исключение
        ЗаписьЖурналаРегистрации("FTP.ОшибкаПеремещения",
            УровеньЖурналаРегистрации.Ошибка, , ,
            "Не удалось переместить файл: " + ИмяФайла + ". Ошибка: " + ОписаниеОшибки());
    КонецПопытки;
    
КонецЦикла;

Данный подход хорош тем, что все операции происходят непосредственно на FTP-сервере без реального скачивания файлов на локальный диск компьютера или сервера 1С, что экономит интернет-трафик и время.

Способ 2. Использование системной функции КопироватьФайл

Мало кто из начинающих разработчиков знает, что глобальный метод платформы КопироватьФайл() умеет работать не только с локальными путями, но и с сетевыми протоколами, включая FTP. При этом на уровне протокола отправка файла через эту команду инициирует инструкцию перезаписи (команда STOR), которая по умолчанию разрешена на большинстве серверов.

Схема этого решения выглядит так: мы копируем файл напрямую из одного сетевого пути в другой (при этом старый файл автоматически перезаписывается), а затем удаляем оригинал из папки-источника.

Рассмотрим, как правильно составить код:


// Настройки подключения к серверу
ПараметрыFTP = "user:password@00.000.00.00:21"; // Шаблон: логин:пароль@адрес:порт

ФайлыXML = СоединениеFTP.НайтиФайлы("/otkuda", "*.xml");

Для Каждого Файл Из ФайлыXML Цикл
    
    // Формируем полные URL-пути для функции КопироватьФайл
    ПолныйПутьИсточник = "ftp://" + ПараметрыFTP + "/otkuda/" + Файл.Имя;
    ПолныйПутьПриемник = "ftp://" + ПараметрыFTP + "/kuda/" + Файл.Имя;
    
    Попытка
        // Функция КопироватьФайл выполнит перезапись целевого файла автоматически
        КопироватьФайл(ПолныйПутьИсточник, ПолныйПутьПриемник);
        
        // После успешного копирования удаляем исходный файл
        СоединениеFTP.Удалить("/otkuda", Файл.Имя);
    Исключение
        ЗаписьЖурналаРегистрации("FTP.Копирование",
            УровеньЖурналаРегистрации.Ошибка, , ,
            "Ошибка при копировании файла " + Файл.Имя + ": " + ОписаниеОшибки());
    КонецПопытки;
    
КонецЦикла;

Важное примечание безопасности: При формировании строки подключения пароль пользователя передается в открытом виде внутри URL-адреса. Если логи ведения вашей системы записывают полные пути вызовов функций, это может стать уязвимостью. Используйте этот метод аккуратно.

Способ 3. Перемещение через локальный временный каталог

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

Проанализируем этот сценарий по шагам:

  1. Скачиваем файл с FTP во временную папку операционной системы с помощью метода Получить().
  2. Загружаем этот файл из временной папки обратно на FTP, но уже в целевой каталог /kuda/ с помощью метода Записать(). Данный метод по умолчанию перезаписывает существующие файлы.
  3. После успешной загрузки удаляем исходный файл из папки /otkuda/ и локальный временный файл.

Реализуем этот надежный алгоритм:


СоединениеFTP = ПодключитьсяКFTPСерверу();
ФайлыXML = СоединениеFTP.НайтиФайлы("/otkuda", "*.xml");

Для Каждого Файл Из ФайлыXML Цикл
    
    // Создаем уникальное имя временного файла на локальном диске
    ВременныйФайл = ПолучитьИмяВременногоФайла(Файл.Расширение);
    
    Попытка
        // Шаг 1. Скачиваем файл во временное хранилище
        СоединениеFTP.Получить("/otkuda/" + Файл.Имя, ВременныйФайл);
        
        // Шаг 2. Записываем файл в папку архива (перезапись произойдет автоматически)
        СоединениеFTP.Записать(ВременныйФайл, "/kuda/" + Файл.Имя);
        
        // Шаг 3. Удаляем исходный файл с FTP-сервера
        СоединениеFTP.Удалить("/otkuda", Файл.Имя);
        
        // Шаг 4. Очищаем локальный временный файл
        УдалитьФайлы(ВременныйФайл);
        
    Исключение
        ЗаписьЖурналаРегистрации("FTP.ЛокальныйОбход",
            УровеньЖурналаРегистрации.Ошибка, , ,
            "Не удалось обработать файл через локальный буфер: " + Файл.Имя + ". Ошибка: " + ОписаниеОшибки());
            
        // Пытаемся удалить временный файл в случае сбоя, чтобы не засорять диск
        Попытка
            УдалитьФайлы(ВременныйФайл);
        Исключение
            // Игнорируем ошибку удаления локального файла
        КонецПопытки;
    КонецПопытки;
    
КонецЦикла;

Способ 4. Изменение настроек на стороне FTP-сервера

Если у вас есть доступ к администрированию целевого FTP-сервера, вы можете решить эту проблему раз и навсегда на системном уровне, не изменяя код в 1С. Для этого необходимо разрешить перезапись файлов при операции переименования (команда RNTO).

Настройки для различных серверов:

Резюме и рекомендации

Мы детально проанализировали проблему работы с FTP в 1С и нашли лучшие пути её решения. Подведем итоги совместной работы:

← На главную