При работе с внешними API и веб-сервисами программисты 1С часто сталкиваются с задачей получения файлов: это могут быть счета в формате PDF, электронные документы XML, изображения или архивы. Распространенной ошибкой является попытка считать тело ответа как обычную строку. В этой статье мы подробно разберем, почему текстовые методы не подходят для бинарных данных, и рассмотрим наиболее надежные способы сохранения файлов без искажений.
Рассмотрим ситуацию, когда сервис возвращает содержимое файла. Если мы используем метод HTTPОтвет.ПолучитьТелоКакСтроку(), платформа пытается интерпретировать последовательность байтов как символы определенной кодировки (например, UTF-8 или Windows-1251). Для текстовых файлов (XML, TXT) это может сработать, но для PDF, PNG или ZIP это губительно. Бинарные файлы содержат байты, которые не имеют строкового представления в стандартных таблицах символов. При попытке конвертации такие байты либо заменяются «символами-заглушками», либо обрезают строку, если встречается символ конца строки в неожиданном месте. В результате файл получается «битым» и не открывается целевым приложением.
Если нам необходимо обработать данные в оперативной памяти перед записью (например, поместить их во временное хранилище для передачи на клиент), мы используем метод ПолучитьТелоКакДвоичныеДанные(). Проанализируем пример кода на стороне сервера:
&НаСервере
Функция ПолучитьФайлИзСервиса(ПараметрыЗапроса)
Соединение = Новый HTTPСоединение("api.service.ru", , , , , 30, Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос("/get_file?id=" + ПараметрыЗапроса.Идентификатор);
Ответ = Соединение.Получить(Запрос);
Если Ответ.КодСостояния = 200 Тогда
// Получаем «сырые» байты без привязки к кодировке текста
ДанныеФайла = Ответ.ПолучитьТелоКакДвоичныеДанные();
// Помещаем во временное хранилище, чтобы передать на клиент
АдресХранилища = ПоместитьВоВременноеХранилище(ДанныеФайла);
Возврат АдресХранилища;
Иначе
ВызватьИсключение "Ошибка сервиса: " + Ответ.КодСостояния;
КонецЕсли;
КонецФункции
После получения адреса на стороне клиента, мы можем сохранить файл в выбранный пользователем каталог:
&НаКлиенте
Процедура СохранитьФайл(АдресХранилища)
Данные = ПолучитьИзВременногоХранилища(АдресХранилища);
// Важно: расширение должно соответствовать типу контента
ПутьКФайлу = "C:\Downloads\document.pdf";
Данные.Записать(ПутьКФайлу);
Сообщить("Файл успешно сохранен: " + ПутьКФайлу);
КонецПроцедуры
Важный нюанс: Если при открытии сохраненного файла вы видите текстовое представление HEX-кодов (например, 3C 3F 78 6D 6C...), это означает, что сам сервер присылает данные в текстовом формате HEX или Base64. В этом случае перед записью данные необходимо декодировать методами Base64Значение() или парсингом HEX-строки.
Наиболее эффективный и безопасный с точки зрения потребления памяти способ — это использование второго параметра метода Получить() объекта HTTPСоединение. В этом случае платформа 1С записывает поток данных из HTTP-ответа напрямую на диск, минуя промежуточные объекты в памяти.
Рассмотрим, как реализовать этот подход. Мы создадим временный файл на сервере, запишем туда ответ, а затем передадим результат пользователю.
&НаСервере
Функция СкачатьФайлНапрямую()
Соединение = Новый HTTPСоединение("example.com");
Запрос = Новый HTTPЗапрос("/download/report.pdf");
// Генерируем уникальное имя временного файла
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
// Указываем путь к файлу как второй параметр метода Получить
Ответ = Соединение.Получить(Запрос, ИмяВременногоФайла);
Если Ответ.КодСостояния = 200 Тогда
// Файл уже записан на диск платформой
Данные = Новый ДвоичныеДанные(ИмяВременногоФайла);
Адрес = ПоместитьВоВременноеХранилище(Данные);
// Удаляем временный файл после помещения в хранилище
УдалитьФайлы(ИмяВременногоФайла);
Возврат Адрес;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Этот метод исключает любые проблемы с кодировками, так как 1С работает с ответом как с FileStream. Если сервер вернул файл, он будет сохранен байт-в-байт в первозданном виде.
Часто мы не знаем заранее, файл какого типа прилетит (PDF или XML). В этом случае проанализируем заголовки HTTP-ответа. Обычно сервер передает информацию в заголовке Content-Type (MIME-тип) или Content-Disposition (рекомендуемое имя файла).
Разберем пример извлечения имени файла:
// В объекте HTTPОтвет есть соответствие Заголовки
ИмяФайлаСервера = Ответ.Заголовки.Получить("Content-Disposition");
// Пример значения: attachment; filename="report_2023.pdf"
Если ИмяФайлаСервера <> Неопределено Тогда
// Простая очистка строки для получения только имени
ЧастиИмени = СтрРазделить(ИмяФайлаСервера, "=");
Если ЧастиИмени.Количество() > 1 Тогда
ЧистоеИмя = СтрЗаменить(ЧастиИмени[1], """", "");
Сообщить("Сервер предлагает имя: " + ЧистоеИмя);
КонецЕсли;
КонецЕсли;
Для корректной работы с произвольными данными в 1С при использовании HTTPСоединение следует придерживаться следующих правил:
ДвоичныеДанные и ВременноеХранилище.Соединение.Получить(Запрос, ИмяВыходногоФайла) — самый стабильный вариант для сохранения файлов большого объема, так как он не перегружает оперативную память сервера.Content-Type, чтобы понимать, в каком формате пришли данные (binary, base64 или hex).Используя эти подходы, вы обеспечите стабильную работу своих интеграций и корректное открытие файлов у конечных пользователей.