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

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

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

Причина проблемы: разделители строк и протокол HTTP

Основная сложность заключается в том, что протокол HTTP жестко регламентирует использование символов перевода строки. Согласно стандартам RFC, каждая строка в заголовках и теле multipart-запроса должна заканчиваться последовательностью CRLF (возврат каретки + перевод строки). В 1С при использовании многострочных строк (через вертикальную черту |) в зависимости от операционной системы или настроек может использоваться только символ ПС (LF). Для многих современных веб-серверов (особенно на базе IIS или строгих конфигураций Nginx/Apache) одиночного символа ПС недостаточно, чтобы распознать окончание заголовка или части данных.

Рассмотрим решение, которое нашел автор темы. Вместо того чтобы полагаться на автоматический перенос строк платформой, необходимо явно добавлять системные символы Символы.ВК + Символы.ПС.


Разделитель = "----WebKitFormBoundary" + Строка(Новый УникальныйИдентификатор());
ПереносСтроки = Символы.ВК + Символы.ПС;

ТелоЗапроса = "--" + Разделитель + ПереносСтроки;
ТелоЗапроса = ТелоЗапроса + "Content-Disposition: form-data; name=""file""; filename=""q.pdf""" + ПереносСтроки;
ТелоЗапроса = ТелоЗапроса + "Content-Type: application/pdf" + ПереносСтроки + ПереносСтроки;
ТелоЗапроса = ТелоЗапроса + "1" + ПереносСтроки; // Здесь должны быть данные файла
ТелоЗапроса = ТелоЗапроса + "--" + Разделитель + "--" + ПереносСтроки;

Обратим внимание на пустую строку (двойной ПереносСтроки) после заголовка Content-Type. Это критически важный момент: пустая строка отделяет метаданные части запроса от ее фактического содержимого.

Работа с двоичными данными (файлами)

В примере выше автор пытался передать содержимое PDF-файла просто как текст («1»). Это допустимо только для тестирования. На практике использование метода УстановитьТелоИзСтроки для передачи бинарных файлов (PDF, PNG, EXE) приведет к повреждению файла, так как 1С попытается интерпретировать байты файла как символы определенной кодировки.

Для правильной сборки запроса с реальным файлом проанализируем использование объекта ЗаписьДанных. Этот метод позволяет последовательно записывать в один поток и текстовые части разделителей, и «сырые» байты файла.


// Создаем поток в памяти для сборки тела
ПотокТела = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(ПотокТела, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Разделитель = "----MyBoundary";
CRLF = Символы.ВК + Символы.ПС;

// 1. Записываем текстовую часть (начало)
ЗаписьДанных.ЗаписатьСтроку("--" + Разделитель + CRLF);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""document.pdf""" + CRLF);
ЗаписьДанных.ЗаписатьСтроку("Content-Type: application/pdf" + CRLF + CRLF);
ЗаписьДанных.СброситьБуфер(); // Выталкиваем текст в поток

// 2. Записываем содержимое файла напрямую из ДвоичныеДанные
ФайлДД = Новый ДвоичныеДанные("C:\temp\document.pdf");
ПотокТела.КопироватьИз(ФайлДД.ОткрытьПотокДляЧтения());

// 3. Записываем финальный разделитель
ЗаписьДанных.ЗаписатьСтроку(CRLF + "--" + Разделитель + "--" + CRLF);
ЗаписьДанных.Закрыть();

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

Важно: При использовании кодировки UTF-8 обязательно указывайте параметр ИспользованиеByteOrderMark.НеИспользовать. Если 1С добавит BOM-символы в начало текстового блока, сервер может не опознать разделитель (boundary), так как он перестанет соответствовать значению, указанному в заголовке Content-Type.

Автоматизация с помощью библиотеки «Коннектор»

Как отметили участники обсуждения, ручная сборка multipart-запросов — процесс трудоемкий и чреват ошибками (например, легко забыть два дефиса в конце последнего разделителя). Профессиональное сообщество рекомендует использовать готовые инструменты, такие как библиотека «Коннектор» (разработка Василия Бондаревского).

Преимущества использования библиотеки:

  1. Автоматическая генерация Boundary: Вам не нужно придумывать уникальную строку.
  2. Управление MIME-типами: Библиотека сама определит, что PDF — это application/pdf.
  3. Безопасная работа с бинарными данными: Исключены ошибки кодирования содержимого файлов.
  4. Поддержка сложных структур: Передача массивов, вложенных полей и больших файлов через потоки.

Однако, как справедливо заметили в комментариях, при использовании сторонних библиотек важно понимать их внутреннее устройство. Некоторые реализации могут не учитывать все многообразие кодов состояния HTTP (Response Status Codes), поэтому всегда полезно иметь возможность заглянуть в исходный код и адаптировать его под свои нужды.

Чек-лист для отладки запроса

Если сервер продолжает выдавать ошибку (например, 400 Bad Request), выполним следующие шаги:

  1. Проанализируем заголовок Content-Type в объекте HTTPЗапрос. Он должен выглядеть так: multipart/form-data; boundary=ваш_разделитель.
  2. Проверим, что в теле запроса перед каждым разделителем стоят два дефиса --.
  3. Убедимся, что самый последний разделитель заканчивается двумя дефисами: --ваш_разделитель--.
  4. Используем Fiddler или Postman для сравнения «правильного» запроса (который работает) и запроса из 1С. В Fiddler обратим внимание на вкладку TextView или HexView, чтобы увидеть скрытые символы 0D 0A (CRLF).
  5. Проверим параметр Content-Length. Если вы формируете тело через ПотокВПамяти, 1С обычно вычисляет размер автоматически, но при ручных манипуляциях со строками и двоичными данными размер может «поплыть» из-за разницы в кодировках.

Выясним причину некорректного парсинга в Fiddler, упомянутую автором: когда Name и Value смешиваются, это почти всегда означает отсутствие пустой строки после блока Content-Disposition. Сервер просто считает, что ваши данные — это продолжение заголовков. Явное использование Символы.ВК + Символы.ПС гарантированно решает эту проблему.

← На главную