В процессе интеграции 1С с внешними веб-сервисами часто возникает задача передачи не только текстовых данных (например, в формате JSON), но и бинарных файлов (PDF, изображения, архивы). В популярных инструментах отладки, таких как Postman, это реализуется простым выбором типа тела запроса form-data. Однако в платформе «1С:Предприятие 8.3» формирование такого запроса требует ручной сборки тела сообщения согласно спецификациям протокола HTTP.
Рассмотрим подробно, как правильно сконструировать запрос типа multipart/form-data, чтобы сервер мог корректно распознать и файл, и сопутствующие параметры. Мы проанализируем наиболее распространенные ошибки, такие как получение кода ответа 422, и разберем проверенные способы решения задачи.
Когда мы отправляем данные через multipart/form-data, тело HTTP-запроса разделяется на несколько частей. Каждая часть содержит собственные заголовки и данные (текстовое поле или содержимое файла). Для разделения этих частей используется уникальная строка-разделитель, называемая boundary. Проанализируем структуру такого тела:
-- плюс значение boundary.Content-Disposition: form-data; name="имя_поля".--, значение boundary и еще два дефиса --.Выясним причину частой ошибки 422 (Unprocessable Entity). Чаще всего она связана с тем, что структура multipart сформирована некорректно: либо не соблюдены правила расстановки пустых строк, либо используются неправильные символы перевода строки. Стандарт HTTP требует использования последовательности CRLF (Возврат каретки + Перевод строки), что в 1С соответствует Символы.ВК + Символы.ПС.
Этот метод считается классическим для 1С. Он заключается в том, что мы создаем несколько временных файлов: текстовый файл с «шапкой» запроса, бинарный файл (сам передаваемый документ) и текстовый файл с «подвалом» запроса. Затем эти файлы объединяются в один общий файл тела, который и отправляется на сервер. Разберем этот алгоритм по шагам.
Сначала подготовим переменные и определим границы запроса:
ИмяФайлаДляЗагрузки = "C:\qwe.pdf";
Сервер = "new.example.kz";
АдресРесурса = "/api/explorer/file";
Boundary = Строка(Новый УникальныйИдентификатор());
МассивФайловДляОбъединения = Новый Массив;
Теперь сформируем текстовую часть, содержащую JSON-параметры и метаданные файла. Важно использовать правильную кодировку (обычно КодировкаТекста.ANSI или UTF-8 в зависимости от требований API):
ИмяФайлаОтправкиНачало = ПолучитьИмяВременногоФайла("txt");
ФайлОтправкиНачало = Новый ЗаписьТекста(ИмяФайлаОтправкиНачало, КодировкаТекста.ANSI, Символы.ВК + Символы.ПС);
// Добавляем параметр "json"
ФайлОтправкиНачало.ЗаписатьСтроку("--" + Boundary);
ФайлОтправкиНачало.ЗаписатьСтроку("Content-Disposition: form-data; name=""json""");
ФайлОтправкиНачало.ЗаписатьСтроку(""); // Пустая строка перед значением
ФайлОтправкиНачало.ЗаписатьСтроку("{}");
// Добавляем параметр "type"
ФайлОтправкиНачало.ЗаписатьСтроку("--" + Boundary);
ФайлОтправкиНачало.ЗаписатьСтроку("Content-Disposition: form-data; name=""type""");
ФайлОтправкиНачало.ЗаписатьСтроку("");
ФайлОтправкиНачало.ЗаписатьСтроку("File1c");
// Оформляем заголовок перед бинарными данными файла
ФайлОтправкиНачало.ЗаписатьСтроку("--" + Boundary);
ФайлОтправкиНачало.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""qwe.pdf""");
ФайлОтправкиНачало.ЗаписатьСтроку("Content-Type: application/pdf");
ФайлОтправкиНачало.ЗаписатьСтроку("");
ФайлОтправкиНачало.Закрыть();
МассивФайловДляОбъединения.Добавить(ИмяФайлаОтправкиНачало);
Далее добавим сам файл и завершающую границу. Посмотрим на пример завершения формирования тела:
// Добавляем файл документа в массив
МассивФайловДляОбъединения.Добавить(ИмяФайлаДляЗагрузки);
// Формируем финальный фрагмент
ИмяФайлаОтправкиКонец = ПолучитьИмяВременногоФайла("txt");
ФайлОтправкиКонец = Новый ЗаписьТекста(ИмяФайлаОтправкиКонец, КодировкаТекста.ANSI, Символы.ВК + Символы.ПС);
ФайлОтправкиКонец.ЗаписатьСтроку("");
ФайлОтправкиКонец.ЗаписатьСтроку("--" + Boundary + "--");
ФайлОтправкиКонец.Закрыть();
МассивФайловДляОбъединения.Добавить(ИмяФайлаОтправкиКонец);
// Объединяем все части в итоговый файл
ИмяФайлаТела = ПолучитьИмяВременногоФайла("tmp");
ОбъединитьФайлы(МассивФайловДляОбъединения, ИмяФайлаТела);
На завершающем этапе создаем HTTPСоединение и выполняем отправку. Обратите внимание на заголовок Content-Type, в котором обязательно должен быть указан boundary.
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
Заголовки.Вставить("Authorization", "Bearer " + МойТокен);
Соединение = Новый HTTPСоединение(Сервер, 443,,,,, Новый ЗащищенноеСоединениеOpenSSL());
ЗапросHTTP = Новый HTTPЗапрос(АдресРесурса, Заголовки);
ЗапросHTTP.УстановитьИмяФайлаТела(ИмяФайлаТела);
Ответ = Соединение.ОтправитьДляОбработки(ЗапросHTTP);
Работа с временными файлами на диске может замедлять работу системы при массовой загрузке документов. Рассмотрим более современный подход с использованием объекта БуферДвоичныхДанных. Это позволяет собрать тело запроса целиком в оперативной памяти.
Проанализируем ситуацию: нам нужно последовательно записать текст и бинарные данные в один поток. Для этого отлично подходит ЗаписьДанных и ПотокВПамяти. Этот метод избавляет от необходимости использовать ОбъединитьФайлы.
ПотокТела = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(ПотокТела, КодировкаТекста.UTF8);
// Записываем текстовую часть
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""name""");
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.ЗаписатьСтроку("TestName");
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""document.pdf""");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: application/pdf");
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.СброситьБуфер(); // Очищаем буфер записи в поток
// Записываем файл из ДвоичныхДанных
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяФайлаДляЗагрузки);
ДвоичныеДанныеФайла.Записать(ПотокТела);
// Записываем финальный Boundary
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + "--");
ЗаписьДанных.Закрыть();
// Получаем тело для запроса
РезультатТело = ПотокТела.ЗакрытьИПолучитьДвоичныеДанные();
ЗапросHTTP.УстановитьТелоИзДвоичныхДанных(РезультатТело);
При реализации передачи файлов важно учитывать следующие моменты, которые помогут избежать ошибок при интеграции:
RFC 2047.УстановитьИмяФайлаТела или УстановитьТелоИзДвоичныхДанных. Не устанавливайте этот заголовок вручную, чтобы избежать расхождений в байтах, особенно если вы используете спецсимволы.РаботаСHTTP. Там реализованы вспомогательные функции для формирования multipart-запросов, которые делают код более лаконичным и безопасным.application/x-octet-stream, лучше указывать точный тип (например, application/pdf, image/png). Некоторые строгие API проверяют соответствие расширения файла и его Content-Type в заголовке части.Разберем еще один совет: для отладки структуры multipart используйте локальный прокси-сервер (например, Fiddler). Настройте HTTPСоединение на работу через прокси и сравните байт в байт запрос из Postman и запрос из 1С. Это позволит моментально увидеть лишние переносы строк или отсутствующие тире в boundary.
Таким образом, мы рассмотрели два надежных способа передачи файлов методом POST. Выбор между ними зависит от версии вашей платформы и требований к производительности. Использование БуфераДвоичныхДанных является более современным стандартом, в то время как ОбъединитьФайлы гарантированно работает на любых версиях 8.3.