Как отправить CSV-файл в кодировке UTF-8 без BOM через POST-запрос в 1С

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

При интеграции 1С со сторонними веб-сервисами часто возникает задача выгрузки табличных данных в формате CSV через протокол HTTP методом POST с составным содержимым multipart/form-data. Нередко при такой отправке сервер возвращает ошибку валидации данных, например: Отсутствует колонка "ID", хотя визуально файл сформирован корректно и первая колонка действительно называется «ID».

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

Выясним причину: почему сервер не видит первую колонку

Если открыть результирующий файл в шестнадцатеричном редакторе или специализированном текстовом редакторе в кодировке UTF-8, можно заметить, что перед первым символом заголовка вставляется неразрывный пробел нулевой ширины — ZWNBSP (символ Юникода U+FEFF). Это метка порядка байтов — BOM (Byte Order Mark), представляющая собой последовательность трех байт: 0xEF, 0xBB, 0xBF.

Платформа 1С при стандартной записи файлов через ЗаписьТекста или ТекстовыйДокумент с указанием кодировки КодировкаТекста.UTF8 по умолчанию всегда добавляет эти три байта в начало потока. В то же время большинство внешних веб-серверов и библиотек парсинга CSV не ожидают сигнатуру BOM и считывают её как часть имени первой колонки. В итоге сервер видит заголовок [BOM]ID вместо ожидаемого ID и отклоняет запрос.

Способ 1. Получение двоичных данных без BOM из строки

Рассмотрим самый компактный и надежный способ сформировать CSV-файл в UTF-8 без добавления сигнатуры BOM. Для этого воспользуемся встроенной функцией ПолучитьДвоичныеДанныеИзСтроки, в которой предусмотрен специальный параметр управления записью BOM:


// Формируем текст CSV в переменной
ТекстCSV = "ID;Наименование;Количество" + Символы.ВК + Символы.ПС;
ТекстCSV = ТекстCSV + "1;Товар 1;10" + Символы.ВК + Символы.ПС;

// Третий параметр (Ложь) отключает добавление метки BOM
ДвоичныеДанныеCSV = ПолучитьДвоичныеДанныеИзСтроки(ТекстCSV, "UTF-8", Ложь);

// Сохраняем чистый UTF-8 во временный файл
ИмяФайлаДляЗагрузки = ПолучитьИмяВременногоФайла("csv");
ДвоичныеДанныеCSV.Записать(ИмяФайлаДляЗагрузки);

Этот способ позволяет гарантированно избавиться от символа ZWNBSP без необходимости ручной обработки байтов.

Способ 2. Запись через объект ЗаписьДанных и потоки

Если объем данных велик и хранить весь текст выгрузки в оперативной памяти нерационально, разберем по шагам потоковую запись. При последовательной записи данных через объект ЗаписьДанных в файл метод ЗаписатьСтроку не добавляет BOM-маркер:


ИмяФайлаCSV = ПолучитьИмяВременногоФайла("csv");

// Открываем файловый поток для записи
ФайловыйПоток = Новый ФайловыйПоток(ИмяФайлаCSV, РежимРаскрытияФайла.Создать, ДоступКФайлу.Запись);
ЗаписьДанных = Новый ЗаписьДанных(ФайловыйПоток);

// Записываем заголовки и строки с явным указанием кодировки UTF-8
ЗаписьДанных.ЗаписатьСтроку("ID;Наименование;Количество", "UTF-8", Символы.ВК + Символы.ПС);
ЗаписьДанных.ЗаписатьСтроку("1;Товар 1;10", "UTF-8", Символы.ВК + Символы.ПС);

// Закрываем запись и поток
ЗаписьДанных.Закрыть();
ФайловыйПоток.Закрыть();

Такой подход обеспечивает высокую скорость работы и предотвращает появление нежелательных байтов в начале файла.

Способ 3. Удаление BOM из уже существующего файла

Если CSV-файл уже сформирован сторонней подсистемой или выгружен типовым механизмом с BOM, разберем, как удалить первые 3 байта перед отправкой:


ДвоичныеДанныеСBOM = Новый ДвоичныеДанные(ИмяИсходногоФайла);
Буфер = ДвоичныеДанныеСBOM.ОткрытьПотокДляЧтения().ПрочитатьВБуферДвоичныхДанных();

// Проверяем наличие сигнатуры UTF-8 BOM (0xEF, 0xBB, 0xBF)
Если Буфер.Размер >= 3 И Буфер[0] = 239 И Буфер[1] = 187 И Буфер[2] = 191 Тогда
    // Создаем буфер нужного размера без первых трех байт
    НовыйБуфер = Новый БуферДвоичныхДанных(Буфер.Размер - 3);
    Буфер.Скопировать(3, НовыйБуфер, 0, Буфер.Размер - 3);
    
    // Перезаписываем файл
    ПотокДляЗаписи = Новый ФайловыйПоток(ИмяИсходногоФайла, РежимРаскрытияФайла.Создать, ДоступКФайлу.Запись);
    ЗаписьДанных = Новый ЗаписьДанных(ПотокДляЗаписи);
    ЗаписьДанных.ЗаписатьБуферДвоичныхДанных(НовыйБуфер);
    ЗаписьДанных.Закрыть();
    ПотокДляЗаписи.Закрыть();
КонецЕсли;

Ошибки при формировании multipart/form-data запроса

Кроме кодировки самого файла, при сборке составного POST-запроса часто допускаются критические ошибки, приводящие к сбоям разбора данных на стороне веб-сервера:

  1. Неправильный символ перевода строк. Стандарт HTTP требует применения связки CRLF (Символы.ВК + Символы.ПС). Использование только Символы.ПС нередко приводит к тому, что веб-сервер не может найти границы блоков multipart.
  2. Ошибки в завершающем разделителе boundary. Промежуточные разделители должны предваряться двумя дефисами ("--" + Boundary), а финальный разделитель закрывается двумя дефисами и спереди, и сзади: "--" + Boundary + "--". Дублирование разделителей в конце файла ломает структуру запроса.
  3. Склеивание файлов через ОбъединитьФайлы. Если при бинарном объединении файлов сам файл CSV не заканчивается переводом строки CRLF, закрывающий boundary пристыкуется непосредственно к последней строке CSV. Это приведет к порче последней записи данных.
  4. Заголовок авторизации. Для передачи токенов доступа стандартным является HTTP-заголовок Authorization (например, Authorization: Bearer <токен>), а не Authenticate.

Итоговый пример правильной сборки и отправки multipart-запроса

Соберем все рекомендации воедино и посмотрим на пример корректного формирования и выполнения HTTP-запроса:


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

// 1. Формируем тело CSV без BOM
ТекстCSV = "ID;Наименование;Количество" + РазделительСтрок;
ТекстCSV = ТекстCSV + "1001;Тестовая позиция;5" + РазделительСтрок;
ДД_CSV = ПолучитьДвоичныеДанныеИзСтроки(ТекстCSV, "UTF-8", Ложь);

ИмяФайлаCSV = ПолучитьИмяВременногоФайла("csv");
ДД_CSV.Записать(ИмяФайлаCSV);

// 2. Формируем начальную часть multipart
ИмяФайлаНачало = ПолучитьИмяВременногоФайла("txt");
ФайлНачало = Новый ЗаписьТекста(ИмяФайлаНачало, КодировкаТекста.ANSI, РазделительСтрок, Ложь);

// Текстовое поле формы
ФайлНачало.ЗаписатьСтроку("--" + Boundary);
ФайлНачало.ЗаписатьСтроку("Content-Disposition: form-data; name=""format""");
ФайлНачало.ЗаписатьСтроку("");
ФайлНачало.ЗаписатьСтроку("smsPersons");

// Заголовок передаваемого файла
ФайлНачало.ЗаписатьСтроку("--" + Boundary);
ФайлНачало.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""upload.csv""");
ФайлНачало.ЗаписатьСтроку("Content-Type: text/csv; charset=utf-8");
ФайлНачало.ЗаписатьСтроку("");
ФайлНачало.Закрыть();

// 3. Формируем завершающую часть multipart
ИмяФайлаКонец = ПолучитьИмяВременногоФайла("txt");
ФайлКонец = Новый ЗаписьТекста(ИмяФайлаКонец, КодировкаТекста.ANSI, РазделительСтрок, Ложь);
ФайлКонец.ЗаписатьСтроку("");
ФайлКонец.ЗаписатьСтроку("--" + Boundary + "--");
ФайлКонец.Закрыть();

// 4. Объединяем части в один файл отправки
ИмяФайлаОтправки = ПолучитьИмяВременногоФайла("bin");
МассивФайлов = Новый Массив;
МассивФайлов.Добавить(ИмяФайлаНачало);
МассивФайлов.Добавить(ИмяФайлаCSV);
МассивФайлов.Добавить(ИмяФайлаКонец);
ОбъединитьФайлы(МассивФайлов, ИмяФайлаОтправки);

// 5. Выполняем HTTP-запрос
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
Заголовки.Вставить("Authorization", "Bearer " + Токен);

HTTPСоединение = Новый HTTPСоединение(Сервер, 443,,,, 30, Новый ЗащищенноеСоединениеOpenSSL);
HTTPЗапрос = Новый HTTPЗапрос(Ресурс, Заголовки);
HTTPЗапрос.УстановитьИмяФайлаТела(ИмяФайлаОтправки);

HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
КодСостояния = HTTPОтвет.КодСостояния;
ТекстОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();

// 6. Удаляем временные файлы
УдалитьФайлы(ИмяФайлаНачало);
УдалитьФайлы(ИмяФайлаCSV);
УдалитьФайлы(ИмяФайлаКонец);
УдалитьФайлы(ИмяФайлаОтправки);

Применение функции ПолучитьДвоичныеДанныеИзСтроки с флагом отключения BOM и использование стандартного разделителя CRLF обеспечивают безошибочную передачу данных и корректное распознавание структуры колонок CSV внешним сервисом.

← На главную