При разработке интеграций часто возникает задача передать не один, а сразу несколько файлов (например, пакет документов или набор изображений) в рамках одного HTTP-запроса. Типичная ошибка новичка — попытка несколько раз вызвать метод УстановитьТелоИзДвоичныхДанных, что технически невозможно, так как каждый последующий вызов просто перезаписывает тело запроса. В этой статье мы подробно разберем несколько стратегий решения этой задачи, начиная от использования современных объектов платформы 1С и заканчивая классическими методами сериализации.
Это стандартный и наиболее правильный с точки зрения протокола HTTP способ. Начиная с версии платформы 1С 8.3.18, разработчикам стали доступны специализированные объекты ЗаписьСоставногоСообщения и ЧтениеСоставногоСообщения, которые значительно упрощают формирование запросов в формате multipart/form-data. Рассмотрим по шагам, как реализовать этот подход.
Преимущества: соответствие мировым стандартам, отсутствие необходимости кодировать бинарные данные в текст (что экономит трафик), возможность задавать метаданные для каждой части сообщения.
Проанализируем пример кода для отправки двух файлов:
// Создаем уникальный разделитель (boundary)
Разделитель = Строка(Новый УникальныйИдентификатор);
// Создаем объект для записи составного сообщения
ЗаписьСоставного = Новый ЗаписьСоставногоСообщения;
ЗаписьСоставного.УстановитьРазделитель(Разделитель);
// Добавляем первый файл
ЗаголовкиФайла1 = Новый Соответствие;
ЗаголовкиФайла1.Вставить("Content-Disposition", "form-data; name=""file1""; filename=""report.pdf""");
ЗаголовкиФайла1.Вставить("Content-Type", "application/pdf");
Часть1 = Новый ПотокВПамяти; // Или чтение из файла
ДвоичныеДанныеФайла1 = Новый ДвоичныеДанные("C:\temp\report.pdf");
ДвоичныеДанныеФайла1.Записать(Часть1);
Часть1.ПерейтиВНачало();
ЗаписьСоставного.ДобавитьЧасть(Часть1, ЗаголовкиФайла1);
// Добавляем второй файл
ЗаголовкиФайла2 = Новый Соответствие;
ЗаголовкиФайла2.Вставить("Content-Disposition", "form-data; name=""file2""; filename=""image.png""");
ЗаголовкиФайла2.Вставить("Content-Type", "image/png");
ДвоичныеДанныеФайла2 = Новый ДвоичныеДанные("C:\temp\image.png");
Часть2 = Новый ПотокВПамяти;
ДвоичныеДанныеФайла2.Записать(Часть2);
Часть2.ПерейтиВНачало();
ЗаписьСоставного.ДобавитьЧасть(Часть2, ЗаголовкиФайла2);
// Формируем итоговое тело запроса
ПотокТела = ЗаписьСоставного.ПолучитьПоток();
// Настраиваем HTTP-запрос
Запрос = Новый HTTPЗапрос("/upload");
Запрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Разделитель);
Запрос.УстановитьТелоИзПотока(ПотокТела);
Выясним причину использования УстановитьТелоИзПотока: этот метод позволяет передавать данные напрямую, не загружая их целиком в оперативную память в виде одного огромного объекта ДвоичныеДанные. Это критически важно, если вы передаете файлы размером в сотни мегабайт.
Если принимающая сторона — это другой веб-сервис, работающий исключительно с JSON, мы можем упаковать файлы в структуру JSON. Однако важно помнить, что бинарные данные нельзя просто вставить в текст — их необходимо закодировать в Base64.
Важный нюанс: кодирование в Base64 увеличивает размер передаваемых данных ровно на 33%. Если ваш файл весил 100 МБ, в JSON-запросе он превратится в 133 МБ. Рассмотрим, как это реализовать:
ДвоичныеДанные.Base64Строка() для преобразования данных в текстовый вид.Посмотрим на пример кода:
ДанныеДляОтправки = Новый Структура;
МассивФайлов = Новый Массив;
// Обрабатываем первый файл
Файл1 = Новый ДвоичныеДанные("C:\docs\contract.docx");
Инфо1 = Новый Структура;
Инфо1.Вставить("ИмяФайла", "contract.docx");
Инфо1.Вставить("Данные", Base64Строка(Файл1));
МассивФайлов.Добавить(Инфо1);
// Обрабатываем второй файл
Файл2 = Новый ДвоичныеДанные("C:\docs\invoice.pdf");
Инфо2 = Новый Структура;
Инфо2.Вставить("ИмяФайла", "invoice.pdf");
Инфо2.Вставить("Данные", Base64Строка(Файл2));
МассивФайлов.Добавить(Инфо2);
ДанныеДляОтправки.Вставить("Files", МассивФайлов);
// Сериализация
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеДляОтправки);
СтрокаЗапроса = ЗаписьJSON.Закрыть();
HTTPЗапрос = Новый HTTPЗапрос("/api/v1/save-files");
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапроса);
Если требования к формату на принимающей стороне позволяют, самым простым и эффективным способом передачи множества файлов будет их предварительная упаковка в один архив. Это решает проблему "один запрос — одно тело" максимально эффективно.
Разберем алгоритм действий (для работы с архивами также может пригодиться специализированный распаковщик и запаковщик файлов):
ЗаписьZipФайла.Добавить().УстановитьТелоИзДвоичныхДанных.Важное замечание о сжатии: как справедливо отмечено в обсуждении, двоичные данные (особенно уже сжатые форматы вроде JPEG или PDF) практически не уменьшаются в объеме при архивации. Смысл архива здесь не в экономии места, а в удобстве передачи всей пачки файлов одним "куском".
Проанализируем ситуацию, когда файлов много и они тяжелые. Использование ДвоичныеДанные может привести к переполнению памяти (ошибка "Недостаточно памяти"). В этом случае следует использовать объект ЧтениеДанных и работу с потоками.
Если вы работаете на платформе ниже 8.3.18 и вам нужно реализовать Multipart вручную, вам придется самостоятельно записывать в поток границы (boundaries) в формате --BoundaryName, заголовок Content-Disposition и саму последовательность байтов файла. Это трудоемкий процесс, требующий внимательности к переносам строк (используйте Символы.ПС или Символы.ВК + Символы.ПС в зависимости от требований сервера).
В ходе дискуссии был поднят вопрос производительности: что быстрее — отправить один большой файл или много маленьких? Практика показывает следующее:
Подводя итог, выберем оптимальный путь решения задачи:
УстановитьТелоИзДвоичныхДанных для работы с файлами, размер которых превышает 10-20 МБ, чтобы избежать проблем с производительностью сервера 1С.Помните, что при ручном формировании multipart-запросов (без встроенных объектов 1С) крайне важно следить за корректностью кодировки текстовых частей и отсутствием лишних пробелов в строках разделителей.