При интеграции 1С с современными веб-сервисами разработчики часто сталкиваются с требованием передавать данные не в строке запроса и не в обычном JSON, а в формате formData (техническое название multipart/form-data). Это стандарт, который позволяет отправлять на сервер одновременно и текстовые поля, и двоичные данные (файлы) в одном теле запроса. В отличие от JavaScript, где существует удобный объект FormData, в платформе 1С:Предприятие 8.3 нам необходимо формировать структуру такого запроса вручную. Рассмотрим подробно, как это сделать правильно.
Прежде чем приступать к написанию кода, проанализируем, из чего состоит такой запрос. Весь запрос представляет собой единый блок данных (тело), разделенный на части специальным маркером — Boundary (разделителем). Каждая часть содержит свои заголовки и само значение параметра.
Согласно стандарту RFC 7578, структура одной части выглядит так:
-- плюс уникальная строка boundary.Content-Disposition: form-data; name="имя_параметра".Выясним причину важности разделителя: он не должен встречаться внутри самих данных, иначе сервер решит, что часть данных закончилась раньше времени. Обычно в 1С его генерируют на основе уникального идентификатора. Разберем пример создания разделителя и настройки заголовков запроса:
Граница = СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Граница);
Важно: Обратите внимание, что в заголовке Content-Type мы указываем разделитель без начальных дефисов, но в самом теле запроса перед ним всегда должны стоять два дефиса --.
Рассмотрим ситуацию, когда нам нужно передать простое текстовое поле в формате formData. Мы будем использовать объект ТекстовыйДокумент или просто конкатенацию строк, но с обязательным соблюдением кодировки UTF-8 и правильных переносов строк (символы ВК + ПС).
// Формируем строковую переменную для тела
РазделительЛиний = Символы.ВК + Символы.ПС;
ТелоЗапросаТекст = "";
// Начало блока параметра
ТелоЗапросаТекст = ТелоЗапросаТекст + "--" + Граница + РазделительЛиний;
ТелоЗапросаТекст = ТелоЗапросаТекст + "Content-Disposition: form-data; name=""volume""" + РазделительЛиний;
ТелоЗапросаТекст = ТелоЗапросаТекст + РазделительЛиний; // Обязательная пустая строка
ТелоЗапросаТекст = ТелоЗапросаТекст + "100"; // Значение параметра
ТелоЗапросаТекст = ТелоЗапросаТекст + РазделительЛиний;
// Завершающий разделитель (в конце добавляется еще два дефиса)
ТелоЗапросаТекст = ТелоЗапросаТекст + "--" + Граница + "--" + РазделительЛиний;
Если сервер требует передать один параметр как текст, а другой как файл, задача усложняется, так как нельзя просто сложить строку и двоичные данные. Проанализируем правильный подход с использованием объекта Поток или временных файлов. Выясним, как собрать "слоеный пирог" из данных.
Посмотрим на пример реализации через двоичные данные:
// 1. Формируем текстовую часть начала
НачалоЗапроса = "--" + Граница + РазделительЛиний;
НачалоЗапроса = НачалоЗапроса + "Content-Disposition: form-data; name=""comment""" + РазделительЛиний + РазделительЛиний;
НачалоЗапроса = НачалоЗапроса + "Текст комментария" + РазделительЛиний;
// 2. Формируем заголовок для файла
НачалоЗапроса = НачалоЗапроса + "--" + Граница + РазделительЛиний;
НачалоЗапроса = НачалоЗапроса + "Content-Disposition: form-data; name=""file""; filename=""document.pdf""" + РазделительЛиний;
НачалоЗапроса = НачалоЗапроса + "Content-Type: application/pdf" + РазделительЛиний + РазделительЛиний;
// Переводим текстовую часть в двоичные данные UTF-8
ДвоичныеНачало = ПолучитьДвоичныеДанныеИзСтроки(НачалоЗапроса, КодировкаТекста.UTF8, Ложь);
// 3. Получаем двоичные данные файла
ДвоичныеФайла = Новый ДвоичныеДанные("C:\temp\document.pdf");
// 4. Формируем финальный разделитель
КонецЗапроса = РазделительЛиний + "--" + Граница + "--" + РазделительЛиний;
ДвоичныеКонец = ПолучитьДвоичныеДанныеИзСтроки(КонецЗапроса, КодировкаТекста.UTF8, Ложь);
// 5. Соединяем всё в один поток или массив
// Для небольших данных можно использовать ЗаписьДанных
ПотокРезультат = Новый ПотокВПамяти();
ПотокРезультат.Записать(ДвоичныеНачало);
ПотокРезультат.Записать(ДвоичныеФайла);
ПотокРезультат.Записать(ДвоичныеКонец);
РезультатДвоичныеДанные = ПотокРезультат.ЗакрытьИПолучитьДвоичныеДанные();
После того как тело сформировано, используем стандартные объекты HTTPСоединение и HTTPЗапрос. Посмотрим, как собрать всё воедино:
Соединение = Новый HTTPСоединение("api.server.com", 443, , , , , Новый ЗащищенноеСоединениеOpenSSL());
Запрос = Новый HTTPЗапрос("/v1/upload", Заголовки);
Запрос.УстановитьТелоИзДвоичныхДанных(РезультатДвоичныеДанные);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Сообщить("Данные успешно переданы!");
КонецЕсли;
Проанализируем основные причины, по которым сервер может возвращать ошибку 400 (Bad Request) при работе с formData:
Content-Disposition и данными всегда должна быть ровно одна пустая строка (два последовательных \r\n).boundary=ABC, в теле используем --ABC для разделения и --ABC-- для завершения запроса.Если ваша конфигурация построена на базе БСП (Библиотека Стандартных Подсистем), рекомендуем проверить наличие модуля ОбщегоНазначения. В актуальных версиях БСП существуют вспомогательные функции для формирования HTTP-запросов. Также стоит обратить внимание на библиотеку "КоннекторHTTP", которая позволяет собирать подобные запросы в стиле fluent interface, избавляя от ручного контроля за дефисами и переносами строк.
Подводя итог, можно сказать, что хотя в 1С нет "волшебной кнопки" для formData, понимание структуры протокола HTTP позволяет реализовать передачу любой сложности стандартными средствами платформы. Главное — внимательно следить за структурой формируемого двоичного потока.