Приветствуем вас, коллеги! Работа с протоколом FTP из встроенного языка 1С:Предприятия — стандартная задача при реализации интеграционных механизмов, обмене данными или выгрузке каталогов товаров на сайт. Однако на практике разработчики часто сталкиваются со специфическими и неочевидными ошибками платформы 1С при вызове методов объекта FTPСоединение. Две самые распространенные проблемы — это отказ в создании каталога с системной ошибкой «Quote command returned error» и сбой проверки существования файлов с ошибкой «Access denied to remote resource».
В этой статье мы подробно разберем, почему возникают эти сбои, как особенности работы FTP-демонов на стороне сервера влияют на поведение 1С, и напишем надежный, отказоустойчивый код для выгрузки файлов и создания структуры каталогов любой вложенности.
При вызове метода СоздатьКаталог() объект FTPСоединение отправляет на FTP-сервер низкоуровневую команду протокола MKD (Make Directory). Ошибка «Quote command returned error» означает, что FTP-сервер вернул код ошибки (чаще всего это код 550) в ответ на этот запрос, а платформа 1С транслировала этот ответ в системное исключение.
Давайте проанализируем основные причины такого поведения сервера:
СоздатьКаталог() в 1С не умеет рекурсивно создавать вложенные подкаталоги. Например, если на сервере есть папка upload, и мы пытаемся выполнить команду Соединение.СоздатьКаталог("upload/import_files/images"), сервер вернет ошибку, так как папки import_files еще не существует. Путь должен создаваться строго пошагово.Эта ошибка часто сбивает разработчиков с толку. Платформа 1С сообщает об отказе в доступе (аутентификации) к удаленному ресурсу при попытке проверить наличие файла или каталога методом НайтиФайлы(). На самом деле реальных причин здесь три:
Во-первых, некорректное использование слэшей на конце путей. Символы прямого слэша / в конце строки пути часто воспринимаются сервером некорректно. Например, путь upload/import_files/ может быть распознан сервером как попытка обратиться к пустому элементу каталога внутри папки import_files. В результате сервер возвращает ошибку синтаксиса или ошибку отсутствия ресурса, которую 1С интерпретирует как проблему с доступом.
Во-вторых, рекурсивный поиск файлов. По умолчанию, если вы вызываете поиск файлов в подкаталогах, 1С посылает серверу команду получения списка с ключом рекурсии (например, LIST -R). В целях безопасности и снижения нагрузки многие публичные и веб-серверы блокируют рекурсивные команды. Сервер отклоняет запрос, а 1С выдает ошибку доступа.
В-третьих, чувствительность к регистру (Case Sensitivity). Если ваш FTP-сервер работает под управлением ОС Linux/Unix (что является стандартом для веб-хостингов), пути upload/import_files и Upload/Import_Files — это абсолютно разные каталоги. Несовпадение хотя бы одного символа в регистре приведет к ошибке доступа или отсутствия ресурса.
Рассмотрим распространенную архитектурную ошибку, которую часто допускают при написании выгрузки. Посмотрим на этот паттерн поведения кода:
// ПЛОХОЙ ПРИМЕР: Смена каталога в цикле выгрузки файлов
Для Каждого НайденныйФайл Из МассивФайлов Цикл
Если НайденныйФайл.ЭтоКаталог() Тогда
// Постоянная смена текущего каталога
Соединение.УстановитьТекущийКаталог("upload/" + НайденныйФайл.Имя);
// Какая-то работа с файлами...
КонецЕсли;
КонецЦикла;
При таком подходе состояние сессии FTP-соединения постоянно меняется. Метод УстановитьТекущийКаталог() изменяет рабочую область. Если внутри цикла происходит сбой или вложенность каталогов увеличивается, относительные пути ломаются. Программа теряет контекст, где именно она сейчас находится на сервере, и последующие вызовы создания папок или записи файлов начинают аварийно завершаться.
Правильное решение: Установить текущий каталог (корневую папку обмена) один раз при инициализации соединения, а всю дальнейшую работу по созданию каталогов и записи файлов вести с использованием полных (абсолютных) путей относительно корня FTP-сервера, избегая постоянного переключения контекста.
Разберем практические шаги по оптимизации кода для стабильного обмена данными с FTP-сервером.
Прежде чем передавать строки путей в методы НайтиФайлы(), СоздатьКаталог() или Записать(), необходимо убедиться, что они не содержат обратных слэшей \ (в FTP используются только прямые слэши /) и не заканчиваются слэшем. Напишем простую вспомогательную функцию:
Функция НормализоватьПутьFTP(Знач ВходнойПуть) Экспорт
// Заменяем все обратные слэши на прямые
Результат = СтрЗаменить(ВходнойПуть, "\", "/");
// Удаляем случайные двойные слэши
Пока Найти(Результат, "//") > 0 Цикл
Результат = СтрЗаменить(Результат, "//", "/");
КонецЦикла;
// Удаляем завершающий слэш, если он есть
Если Прав(Результат, 1) = "/" Тогда
Результат = Сред(Результат, 1, СтрДлина(Результат) - 1);
КонецЕсли;
Возврат Результат;
КонецФункции
В 90% случаев проблемы с зависанием поиска файлов или неожиданными ошибками «Access denied» на современных серверах решаются принудительной установкой пассивного режима соединения. В пассивном режиме клиент (1С) сам инициирует порты для передачи данных, что позволяет успешно обходить ограничения брандмауэров и NAT у провайдера.
// Инициализация соединения с пассивным режимом (6-й параметр равен Истина)
Соединение = Новый FTPСоединение(ФТПХост, ФТППорт, ФТППользователь, ФТППароль, , Истина);
Поскольку стандартный метод СоздатьКаталог() не умеет создавать вложенные цепочки папок за раз, напишем процедуру, которая будет безопасно и пошагово создавать всю структуру папок на сервере, предварительно проверяя их существование.
Процедура СоздатьКаталогНаСервереРекурсивно(Соединение, Знач ПолныйПутьНаСервере) Экспорт
ПолныйПутьНаСервере = НормализоватьПутьFTP(ПолныйПутьНаСервере);
ЧастиПути = СтрРазделить(ПолныйПутьНаСервере, "/");
ТекущийПроверяемыйПуть = "";
Для Каждого ЧастьПути Из ЧастиПути Цикл
Если ПустаяСтрока(ЧастьПути) Тогда
Продолжить;
КонецЕсли;
ТекущийПроверяемыйПуть = ТекущийПроверяемыйПуть + "/" + ЧастьПути;
ТекущийПроверяемыйПуть = НормализоватьПутьFTP(ТекущийПроверяемыйПуть);
// Проверяем существование каталога на этом уровне
Попытка
МассивФайлов = Соединение.НайтиФайлы(ТекущийПроверяемыйПуть, "");
КаталогСуществует = (МассивФайлов.Количество() > 0);
Исключение
// Если НайтиФайлы упал с ошибкой, считаем, что каталога нет
КаталогСуществует = Ложь;
КонецПопытки;
Если Не КаталогСуществует Тогда
Попытка
Соединение.СоздатьКаталог(ТекущийПроверяемыйПуть);
Сообщить("Успешно создан каталог: " + ТекущийПроверяемыйПуть);
Исключение
// Ошибку игнорируем только если папка создалась параллельным процессом
ВызватьИсключение "Не удалось создать каталог " + ТекущийПроверяемыйПуть + ". Причина: " + ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Теперь объединим все наши наработки в единый и надежный алгоритм. Наша задача — выгрузить файлы из локального каталога D:\1С\webdata на FTP-сервер в папку upload/import_files, воссоздавая структуру папок и избегая переключения контекста через УстановитьТекущийКаталог.
Процедура ВыполнитьВыгрузкуНаFTP()
// 1. Инициализация параметров
ФТПХост = "185.41.162.114";
ФТППорт = 21;
ФТППользователь = "admin";
ФТППароль = "password";
ЛокальнаяПапкаВыгрузки = "D:\1С\webdata";
БазовыйКаталогНаFTP = "upload/import_files";
// 2. Устанавливаем соединение в пассивном режиме
Попытка
Соединение = Новый FTPСоединение(ФТПХост, ФТППорт, ФТППользователь, ФТППароль, , Истина);
Сообщить("Соединение с FTP-сервером успешно установлено.");
Исключение
Сообщить("Не удалось подключиться к FTP-серверу: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// 3. Получаем список локальных файлов для выгрузки (включая подкаталоги)
ЛокальныеФайлы = НайтиФайлы(ЛокальнаяПапкаВыгрузки, "*", Истина);
Для Каждого ФайлИмя Из ЛокальныеФайлы Цикл
// Вычисляем относительный путь файла для воссоздания структуры на FTP
ОтносительныйПуть = СтрЗаменить(ФайлИмя.Путь, ЛокальнаяПапкаВыгрузки, "");
ОтносительныйПуть = НормализоватьПутьFTP(ОтносительныйПуть);
// Формируем целевой путь каталога на FTP-сервере
ЦелевойКаталогFTP = БазовыйКаталогНаFTP + "/" + ОтносительныйПуть;
ЦелевойКаталогFTP = НормализоватьПутьFTP(ЦелевойКаталогFTP);
Если ФайлИмя.ЭтоКаталог() Тогда
// Если это каталог — создаем его рекурсивно на сервере
Попытка
СоздатьКаталогНаСервереРекурсивно(Соединение, ЦелевойКаталогFTP + "/" + ФайлИмя.Имя);
Исключение
Сообщить("Ошибка при создании папки: " + ОписаниеОшибки());
КонецПопытки;
Иначе
// Если это файл — сначала убедимся, что папка для него создана
Попытка
СоздатьКаталогНаСервереРекурсивно(Соединение, ЦелевойКаталогFTP);
// Записываем файл напрямую, указывая полный путь назначения на FTP
ИмяФайлаНаСервере = ЦелевойКаталогFTP + "/" + ФайлИмя.Имя;
ИмяФайлаНаСервере = НормализоватьПутьFTP(ИмяФайлаНаСервере);
Соединение.Записать(ФайлИмя.ПолноеИмя, ИмяФайлаНаСервере);
Сообщить("Успешно выгружен файл: " + ИмяФайлаНаСервере);
Исключение
Сообщить("Ошибка при выгрузке файла " + ФайлИмя.Имя + ": " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Сообщить("Процесс выгрузки успешно завершен.");
КонецПроцедуры
Мы подробно разобрали особенности работы платформы 1С с протоколом FTP и выяснили, что большинство ошибок вызваны мелкими деталями форматирования путей и ограничениями самих FTP-серверов. Использование пассивного режима, предварительная очистка путей от конечных слэшей и отказ от постоянной смены текущего каталога в пользу прямой абсолютной адресации гарантируют стабильную и бесперебойную работу ваших интеграционных механизмов.