Как правильно сформировать и отправить HTTP-запрос multipart/form-data в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интеграции 1С с внешними веб-сервисами (API) одной из самых трудоемких задач является передача файлов и сложных структур данных через протокол HTTP. Формат multipart/form-data требует строгого соблюдения структуры тела запроса, где любая лишняя строка или неверный символ переноса могут привести к тому, что сервер не сможет «увидеть» передаваемые данные. В этой статье мы подробно разберем, как избежать типичных ошибок и правильно собрать запрос вручную.

Анализ структуры multipart-запроса

Для начала проанализируем, из чего состоит стандартный запрос этого типа. Представим тело запроса как многослойный пирог, где слои разделены специальной строкой — «границей» (boundary). Каждая часть (part) запроса должна содержать свои собственные метаданные. Рассмотрим основные правила:

  1. Разделитель (Boundary): Это уникальная строка, которая не встречается в самих данных. В начале каждой части она предваряется двумя дефисами (--boundary), а в самом конце всего тела запроса — дефисами и в начале, и в конце (--boundary--).
  2. Заголовки части: Сразу после разделителя должны идти технические данные о части: Content-Disposition (с указанием имени поля и имени файла) и Content-Type (тип контента, например, image/jpeg).
  3. Пустая строка: Это критически важный момент. Между заголовками части и самими данными обязательно должна быть одна пустая строка.
  4. Данные: Бинарное содержимое файла или текстовое значение параметра.

Проблема окончаний строк (CRLF)

Одной из самых частых причин ошибок является использование неверных символов переноса строки. Протокол HTTP требует использования пары CRLF (Возврат каретки + Перевод строки). В коде 1С это соответствует комбинации Символы.ВК + Символы.ПС.

Если мы используем метод ЗаписатьСтроку() объекта ЗаписьДанных, платформа может подставить системное окончание строки, которое зависит от операционной системы сервера (Windows или Linux). Чтобы гарантировать корректность, разберем пример правильного формирования блока:


// Пример формирования заголовков внутри части
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайла + """");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: image/jpeg");
ЗаписьДанных.ЗаписатьСтроку(""); // Та самая обязательная пустая строка
ЗаписьДанных.Записать(ДвоичныеДанныеФайла);
ЗаписьДанных.ЗаписатьСтроку(""); // Перенос перед следующим разделителем

Почему не стоит задавать Content-Length вручную

Проанализируем ситуацию, с которой столкнулся автор темы. Одной из ключевых правок, которая привела к успеху, стало удаление заголовка Content-Length из основного запроса. Выясним причину, почему это происходит.

Когда мы вызываем метод ЗапросHTTP.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТело), платформа 1С самостоятельно вычисляет размер переданного массива байтов и автоматически добавляет корректный заголовок Content-Length. Если программист пытается рассчитать этот размер вручную, легко ошибиться на несколько байт (например, не учесть невидимые символы или маркер BOM в кодировке UTF-8). В результате сервер получает неверную информацию о длине потока, что приводит к обрыву соединения или игнорированию данных.

Важное правило: Доверяйте расчет длины контента платформе, не устанавливайте этот заголовок вручную в Заголовки.Вставить().

Пошаговое руководство по реализации отправки файла

Рассмотрим полный цикл подготовки и отправки данных. Мы будем использовать ПотокВПамяти, чтобы эффективно собрать тело запроса из текстовых строк и бинарных данных файла.

Шаг 1: Подготовка бинарного контента

Перед сборкой запроса необходимо получить двоичные данные файла. Если файл находится во временном хранилище, извлечем его:

ДанныеФайла = ПолучитьИзВременногоХранилища(АдресДвоичныхДанных);

Шаг 2: Формирование тела в потоке

Используем ЗаписьДанных для последовательной записи частей запроса. Обратите внимание на кодировку: лучше всего использовать КодировкаТекста.UTF8 без использования BOM (Byte Order Mark), так как лишние байты в начале потока могут сбить сервер с толку.


Разделитель = "MyUniqueBoundary" + СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
ПотокТело = Новый ПотокВПамяти();
// Создаем запись данных, явно указывая отсутствие BOM
ЗаписьДанных = Новый ЗаписьДанных(ПотокТело, КодировкаТекста.UTF8, ПорядокБайтов.BigEndian, "", Ложь);

// 1. Начальный разделитель
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель);

// 2. Заголовки части файла
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""image.jpg""");
ЗаписьДанных.ЗаписатьСтроку("Content-Type: image/jpeg");
ЗаписьДанных.ЗаписатьСтроку(""); // Пустая строка перед данными

// 3. Бинарные данные
ЗаписьДанных.Записать(ДанныеФайла);

// 4. Завершающий разделитель
ЗаписьДанных.ЗаписатьСтроку("");
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель + "--");

ЗаписьДанных.Закрыть();
РезультатТело = ПотокТело.ЗакрытьИПолучитьДвоичныеДанные();

Шаг 3: Настройка HTTP-запроса и отправка

Теперь соберем объект HTTPЗапрос. Помните, что заголовок Content-Type должен содержать не только тип multipart/form-data, но и само определение границы boundary.


Запрос = Новый HTTPЗапрос("/api/upload/");
Запрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Разделитель);

// Устанавливаем тело, Content-Length добавится автоматически
Запрос.УстановитьТелоИзДвоичныхДанных(РезультатТело);

Соединение = Новый HTTPСоединение("example.com");
Ответ = Соединение.ОтправитьДляОбработки(Запрос);

Если Ответ.КодСостояния = 200 Тогда
    Сообщить("Файл успешно отправлен!");
Иначе
    Сообщить("Ошибка отправки: " + Ответ.ПолучитьТелоКакСтроку());
КонецЕсли;

Дополнительные рекомендации

Проанализируем еще несколько важных аспектов, которые помогут сделать ваш код более надежным:

  1. Имена полей: Убедитесь, что параметр name="file" в заголовке Content-Disposition совпадает с тем, что ожидает сервер. Часто разработчики путают name (имя переменной в API) и filename (имя самого файла). Если name будет указан неверно, сервер вернет ошибку о пустом файле.
  2. Кириллица в именах файлов: Если имя файла содержит русские буквы, некоторые сервера могут их не обработать. В таких случаях рекомендуется использовать КодироватьСтроку(ИмяФайла, СпособКодированияСтроки.URLКодирование) или передавать имя в латинице.
  3. Отладка: Если запрос все равно не проходит, используйте инструменты для перехвата трафика (например, Fiddler). Сравните запрос из 1С с эталонным запросом, например, из программы Postman. Это позволит увидеть скрытые символы или лишние переносы строк.

Таким образом, успех при работе с multipart/form-data в 1С зависит от ювелирной точности в расстановке разделителей и пустых строк, а также от правильного использования объектов ПотокВПамяти и ЗаписьДанных. Отказ от ручного управления Content-Length является ключом к избеганию большинства ошибок при передаче бинарного контента.

← На главную