При разработке интеграций часто возникает задача передать в 1С не просто текстовое сообщение, а сложный пакет данных, содержащий одновременно структуру JSON и один или несколько файлов (изображения, документы). Стандартным способом для такой передачи является формат multipart/form-data. Однако при тестировании через популярный инструмент Postman разработчики часто сталкиваются с тем, что код, написанный по примерам из ИТС, отказывается корректно читать тело запроса. Разберем эту ситуацию подробно, выясним причины ошибок и реализуем надежный механизм парсинга.
Для начала проанализируем, как выглядит «сырое» тело запроса, которое приходит в наш HTTP-сервис. В формате multipart/form-data всё тело разделено на логические блоки специальной строкой-разделителем (boundary). Рассмотрим пример типичного запроса, отправленного из Postman:
----------------------------959821427642407330468393
Content-Disposition: form-data; name="improvementRequest"
Content-Type: application/json
{
"author": { "phoneNumber": "+79991569033", "firstName": "Иван" },
"organisation": "ТК «СОЛНЕЧНЫЙ ДАР»"
}
----------------------------959821427642407330468393
Content-Disposition: form-data; name="files"; filename="image.jpg"
Content-Type: image/jpeg
[Двоичные данные файла]
----------------------------959821427642407330468393--
Важно обратить внимание на разделитель. Согласно спецификациям RFC 2046 и RFC 7578, в заголовке Content-Type указывается базовый разделитель, но внутри тела перед каждым блоком к нему добавляются два дефиса --, а в самом конце — два дефиса в начале и в конце. Проблема многих примеров в том, что они ожидают разделитель в формате ==разделитель, в то время как Postman и большинство современных систем используют стандартные --.
Прежде чем приступать к коду в 1С, проанализируем настройки на стороне клиента. Тестирование HTTP-сервисов показывает, что одной из критических ошибок является ручное указание заголовка Content-Type со значением multipart/form-data во вкладке Headers.
Важное правило: никогда не прописывайте этот заголовок вручную в Postman. Если вы его укажете сами, Postman не сможет динамически сгенерировать уникальный boundary, и сервер 1С не поймет, где начинаются и заканчиваются части данных. Правильно будет выбрать во вкладке Body тип form-data, и тогда Postman сам сформирует нужные заголовки при отправке.
Для детальной отладки рекомендуется использовать Postman Console (сочетание клавиш Alt + Ctrl + C). Там можно увидеть реальное содержимое тела запроса со всеми служебными символами, что позволит сразу выявить проблему несоответствия разделителей, а также использовать инструменты просмотра JSON.
Теперь перейдем к реализации серверной части. Основная задача — получить тело запроса как ДвоичныеДанные, найти в них маркеры-разделители и разрезать общий буфер на части. Рассмотрим обновленный алгоритм, который учитывает особенности формирования разделителей в Postman.
Создадим функцию ПрочитатьСообщение, которая будет принимать двоичные данные тела запроса и строку разделителя (ее можно достать из заголовков запроса).
&НаСервере
Функция ПрочитатьСообщение(Тело, Разделитель)
// Подготавливаем массив возможных маркеров для разделения данных
// Разные клиенты могут добавлять разные символы перевода строки (CRLF)
Маркеры = Новый Массив();
// Стандартные маркеры с дефисами (для Postman)
Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("--" + Разделитель));
Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("--" + Разделитель + Символы.ПС));
Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("--" + Разделитель + Символы.ВК));
Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("--" + Разделитель + Символы.ВК + Символы.ПС));
Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("--" + Разделитель + "--"));
// Дополнительные маркеры (для совместимости с другими системами)
Маркеры.Добавить(ПолучитьБуферДвоичныхДанныхИзСтроки("==" + Разделитель));
ТаблицаФайлов = Новый ТаблицаЗначений;
ТаблицаФайлов.Колонки.Добавить("ДвоичныеДанные");
ТаблицаФайлов.Колонки.Добавить("ИмяФайла");
// Используем ЧтениеДанных для работы с потоком двоичных данных
ЧтениеДанных = Новый ЧтениеДанных(Тело);
// Пропускаем первый разделитель в начале потока
ЧтениеДанных.ПропуститьДо(Маркеры);
ОбщийБуфер = ЧтениеДанных.ПрочитатьВБуферДвоичныхДанных();
// Разделяем общий буфер на массив буферов по маркерам
БуферыЧастей = ОбщийБуфер.Разделить(Маркеры);
Результат = Новый Структура("ДанныеJSON, Файлы", Неопределено, ТаблицаФайлов);
Для Каждого БуферИзЧасти Из БуферыЧастей Цикл
ПотокЧасти = Новый ПотокВПамяти(БуферИзЧасти);
ЧтениеЧасти = Новый ЧтениеДанных(ПотокЧасти);
// Сначала читаем заголовки конкретной части (имя поля, тип контента)
ЗаголовкиЧасти = ПрочитатьЗаголовкиБлока(ЧтениеЧасти);
СведенияОКонтенте = РазведатьContentDisposition(ЗаголовкиЧасти);
Если СведенияОКонтенте.name = "improvementRequest" Тогда
// Если это текстовое поле с JSON, читаем его как строку
СтрокаJSON = ЧтениеЧасти.ПрочитатьСимволы();
Результат.ДанныеJSON = СтрокаJSON;
ИначеЕсли ЗначениеЗаполнено(СведенияОКонтенте.filename) Тогда
// Если в заголовке указано имя файла - сохраняем как двоичные данные
НовыйФайл = ТаблицаФайлов.Добавить();
НовыйФайл.ДвоичныеДанные = ЧтениеЧасти.Прочитать().ПолучитьДвоичныеДанные();
НовыйФайл.ИмяФайла = СведенияОКонтенте.filename;
КонецЕсли;
ЧтениеЧасти.Закрыть();
КонецЦикла;
Возврат Результат;
КонецФункции
Внутри каждого блока данных multipart содержатся свои собственные мини-заголовки. Нам критически важно прочитать их, чтобы понять, что именно находится в текущем блоке: основная информация или файл. Проанализируем вспомогательную функцию для чтения этих заголовков:
Функция ПрочитатьЗаголовкиБлока(ЧтениеЧасти)
Заголовки = Новый Соответствие();
Пока Истина Цикл
Стр = ЧтениеЧасти.ПрочитатьСтроку();
Если Стр = "" Тогда // Пустая строка означает окончание секции заголовков
Прервать;
КонецЕсли;
ПозицияРазделителя = СтрНайти(Стр, ":");
Если ПозицияРазделителя > 0 Тогда
Имя = СокрЛП(Лев(Стр, ПозицияРазделителя - 1));
Значение = СокрЛП(Сред(Стр, ПозицияРазделителя + 1));
Заголовки.Вставить(Имя, Значение);
КонецЕсли;
КонецЦикла;
Возврат Заголовки;
КонецФункции
Рассмотрим подробнее заголовок Content-Disposition. Именно в нем передаются имена полей формы (параметр name) и оригинальные имена файлов (параметр filename). Важно учитывать, что если имя файла содержит кириллицу, Postman может передать его в кодированном виде (RFC 5987), например: filename*=UTF-8''%D0%A3.jpg. Для полноценного решения потребуется декодирование таких строк, однако в базовом варианте достаточно извлечь текст между кавычками.
Часто в комментариях советуют просто кодировать файлы в Base64 и передавать их внутри обычного JSON. Разберем, почему способ multipart/form-data, который мы реализовали выше, лучше:
Base64 увеличивает размер файла примерно на 33%. При передаче больших изображений или тяжелых документов это создает лишнюю нагрузку на сеть и память сервера.ЧтениеДанных и БуферДвоичныхДанных в 1С позволяют работать с файлами максимально эффективно на низком уровне.Multipart является стандартом де-факто для загрузки файлов в веб-технологиях и интеграции 1С с внешними системами.Проанализируем типичные причины, по которым файлы могут приходить «битыми»:
ДвоичныхДанных.JSON из потока используется правильная кодировка (обычно UTF-8). Используйте метод ЧтениеJSON.ОткрытьПоток() для корректной интеграции с полученными данными.Таким образом, мы выяснили, что ключ к успешной отладке запросов из Postman кроется в правильной обработке разделителей блоков и внимательном чтении заголовков каждой части multipart сообщения. Использование БуферДвоичныхДанных делает этот процесс быстрым и надежным.