При интеграции 1С с внешними веб-сервисами (маркетплейсами, сайтами, учетными системами) программисты часто сталкиваются с необходимостью передачи файлов (изображений, документов) методом POST с использованием формата multipart/form-data. Несмотря на кажущуюся простоту, этот процесс требует строгого соблюдения стандартов HTTP-протокола. Ошибка "unexpected end of multipart data" является одной из самых распространенных и сигнализирует о том, что сервер получил некорректно сформированный поток данных. Разберем детально, как правильно организовать отправку файлов и какие инструменты для этого существуют в экосистеме 1С.
Проанализируем ситуацию, представленную в исходном вопросе. Программист пытается вручную собрать тело запроса, используя ПотокВПамяти и ЗаписьДанных. Однако в коде допущена критическая логическая ошибка в цикле обработки картинок:
Пока ВыборкаКартинок.Следующий() Цикл
Транспорт = СоздатьСообщение_Изображение(...);
ЗаписьДанных.Записать(Транспорт);
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель + "--"); // ОШИБКА ЗДЕСЬ
КонецЦикла;
В соответствии со спецификацией RFC 7578, тело multipart запроса должно иметь четкую структуру:
--boundary.Content-Disposition).--boundary-- (с двумя дефисами в конце) только один раз в самом конце всего HTTP-сообщения.В примере автора финальный разделитель записывается на каждой итерации цикла. Сервер, прочитав первую картинку и увидев --boundary--, считает запрос завершенным. Если после этого в потоке продолжают идти данные других файлов, парсер сервера выдает ошибку о неожиданном завершении или некорректных данных. Выясним, как это исправить.
Если мы хотим оставить "чистый" код на 1С без использования внешних библиотек, нам нужно пересмотреть логику сборки потока. Рассмотрим правильный алгоритм:
Шаг 1. Подготовка границ. Важно помнить, что в заголовке Content-Type указывается сам boundary, а в теле сообщения он всегда предваряется двумя дефисами.
Шаг 2. Корректные переводы строк. HTTP-серверы ожидают последовательность CRLF (символы ВК + ПС). При использовании метода ЗаписатьСтроку() на сервере под управлением Linux может записаться только LF, что приведет к ошибке парсинга на стороне веб-сервера (например, Nginx). Рекомендуется явно указывать разделитель.
// Пример правильной сборки тела
Разделитель = "----" + Строка(Новый УникальныйИдентификатор());
ЗаголовкиЗапроса = Новый Соответствие();
ЗаголовкиЗапроса.Вставить("Content-Type", "multipart/form-data; boundary=" + Разделитель);
ПотокТела = Новый ПотокВПамяти();
Запись = Новый ЗаписьДанных(ПотокТела, КодировкаТекста.UTF8, РазделительСтрок.ВК + РазделительСтрок.ПС);
// Записываем текстовое поле
Запись.ЗаписатьСтроку("--" + Разделитель);
Запись.ЗаписатьСтроку("Content-Disposition: form-data; name=""productId""");
Запись.ЗаписатьСтроку(""); // Обязательная пустая строка перед данными
Запись.ЗаписатьСтроку(ИдентификаторТовара);
// Записываем файлы в цикле
Пока Выборка.Следующий() Цикл
Запись.ЗаписатьСтроку("--" + Разделитель);
Запись.ЗаписатьСтроку("Content-Disposition: form-data; name=""image""; filename=""" + Выборка.ИмяФайла + """");
Запись.ЗаписатьСтроку("Content-Type: image/jpeg");
Запись.ЗаписатьСтроку("");
ДанныеФайла = Выборка.СсылкаНаФайл.Получить(); // Получаем ДвоичныеДанные
Запись.Записать(ДанныеФайла);
Запись.ЗаписатьСтроку(""); // Перевод строки после бинарных данных
КонецЦикла;
// ВАЖНО: Финальная граница только ПОСЛЕ цикла
Запись.ЗаписатьСтроку("--" + Разделитель + "--");
Запись.Закрыть();
ДанныеДляОтправки = ПотокТела.ЗакрытьИПолучитьДвоичныеДанные();
Как отмечают участники обсуждения, разработка и поддержка собственного "велосипеда" для HTTP-запросов трудозатратна. Существуют проверенные сообществом решения, такие как библиотека Connector (vbondarevsky). Она инкапсулирует всю сложность формирования multipart сообщений.
Проанализируем преимущества этого подхода:
Пример использования (условный синтаксис библиотеки):
Запрос = Коннектор.HTTPЗапрос();
Запрос.ДобавитьПараметр("productId", ИдентификаторТовара, ТипПараметраЗапросаHTTP.ДанныеФормы);
Для Каждого Картинка Из МассивКартинок Цикл
Запрос.ДобавитьФайл("image", Картинка.ПутьКФайлу, Картинка.ИмяФайла);
КонецЦикла;
Ответ = Коннектор.Post(URL, Запрос);
Для систем на базе Windows можно использовать возможности .NET Framework. Это позволяет использовать высокоуровневые классы, такие как MultipartFormDataContent, которые автоматически формируют запрос по всем стандартам.
Рассмотрим пример кода, который демонстрирует использование HttpClient:
HttpClient = Врап.СоздатьОбъект("System.Net.Http.HttpClient");
Контент = Врап.СоздатьОбъект("System.Net.Http.MultipartFormDataContent");
// Добавление текстового контента
ProductIdContent = Врап.СоздатьОбъект("System.Net.Http.StringContent", ИдентификаторТовара);
Контент.Add(ProductIdContent, "productId");
// Добавление файла через поток
FileStream = Врап.ПолучитьТип("System.IO.File").OpenRead(ПутьКФайлу);
FileContent = Врап.СоздатьОбъект("System.Net.Http.StreamContent", FileStream);
Контент.Add(FileContent, "image", "photo.jpg");
// Отправка
Результат = HttpClient.PostAsync(АдресРесурса, Контент).Result;
Важное замечание: Хотя этот метод выглядит мощным, он делает вашу конфигурацию зависимой от ОС Windows и наличия определенных библиотек .NET. Если планируется переход на сервер 1С под Linux, лучше использовать Решение 1 или 2.
В ходе обсуждения также затронута тема сжатия ответов сервера. Если веб-сервис возвращает данные в формате GZip, 1С (в зависимости от версии) может не уметь автоматически их распаковывать при чтении тела ответа. Рассмотрим ситуацию, когда нужно вручную обработать сжатый поток. Один из способов — имитировать структуру ZIP архива вокруг GZip данных, так как 1С умеет работать с ZIP штатно.
Проанализируем метод распаковки, предложенный в сообщении 23. Он заключается в пропуске префикса GZip (10 байт) и добавлении заголовков, понятных для объекта ЧтениеZipФайла. Однако стоит помнить, что современные версии платформы 1С (8.3.10+) поддерживают работу со сжатием более нативно через объекты ЧтениеДанных и работу с потоками, поэтому перед реализацией сложных схем проверьте возможности вашей текущей платформы.
Для успешной отправки файлов методом multipart/form-data из 1С необходимо соблюдать следующие правила:
--, а финальная заканчивается на --. Убедитесь, что финальная граница не дублируется внутри цикла.ВК + ПС (CRLF) для разделения заголовков и частей сообщения.УстановитьТелоИзПотока() вместо УстановитьТелоИзДвоичныхДанных(). Это убережет сервер 1С от критического потребления оперативной памяти.