Как исправить ошибку "Content is not allowed in prolog" при отправке XML через HTTP-соединение в 1С?

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

При интеграции 1С с внешними веб-сервисами (особенно разработанными на Java, такими как SAP PI/PO, Apache Camel и др.) разработчики часто сталкиваются с аппаратной ошибкой сервера 500 Internal Server Error, сопровождающейся текстом org.xml.sax.SAXParseException: Content is not allowed in prolog. Эта ошибка означает, что XML-парсер принимающей стороны обнаружил посторонние символы перед объявлением XML-документа (прологом) <?xml version="1.0" encoding="UTF-8"?>. Даже один невидимый символ перед открывающей скобкой приведет к падению парсера.

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

Причина №1: Невидимый маркер BOM (Byte Order Mark) в файле UTF-8

По умолчанию при записи текстовых файлов или XML в кодировке UTF-8 платформа 1С добавляет в самое начало файла специальную метку порядка байтов — BOM (Byte Order Mark), которая состоит из трех невидимых байтов (EF BB BF). Веб-браузеры и текстовые редакторы их игнорируют, но строгие XML-парсеры принимающих серверов считывают эти байты как текст перед прологом, что и вызывает исключение Content is not allowed in prolog.

В старых версиях платформы (например, 1С:Предприятие 8.2) метод ЗаписьXML.ОткрытьФайл() не имел встроенных средств для отключения BOM. В современных версиях 1С 8.3 этот параметр присутствует, но разработчики часто забывают его использовать.

Причина №2: Ошибки при ручном формировании запроса Multipart/form-data

Если веб-сервер принимает данные в формате multipart/form-data (передача файлов в теле запроса), разработчики часто собирают тело POST-запроса вручную, объединяя текстовые заголовки границ (boundary) и сам XML-файл. В этом процессе легко допустить синтаксические ошибки:

  1. Отсутствие обязательного двоеточия в заголовках (например, запись Content-typeapplication/xml вместо Content-Type: application/xml).
  2. Отсутствие пустой строки (символов перевода строки CRLF) между заголовками multipart-части и началом самого XML-кода.
  3. Некорректная структура финального разделителя (в конце тела запроса должен быть закрывающий boundary вида --boundary--).
  4. Использование multipart/form-data там, где сервер ожидает обычный "сырой" (raw) XML в теле запроса.

---

Решение проблемы с BOM в 1С:Предприятие 8.3

Проанализируем самый простой и правильный способ записи XML без BOM в современных версиях платформы 1С. Для этого при открытии файла в объекте ЗаписьXML необходимо явно передать третий параметр ДобавитьBOM как Ложь.

Рассмотрим пример создания XML-документа без BOM:


// Формируем путь к временному файлу XML
ИмяФайлаXML = ПолучитьИмяВременногоФайла("xml");

ЗаписьXML = Новый ЗаписьXML;
// Третий параметр "Ложь" отключает добавление BOM-байтов в начало файла
ЗаписьXML.ОткрытьФайл(ИмяФайлаXML, "UTF-8", Ложь);

// Записываем пролог XML
ЗаписьXML.ЗаписатьОбъявлениеXML();

// Записываем структуру XML-документа
ЗаписьXML.ЗаписатьНачалоЭлемента("SetStockRequest");
ЗаписьXML.ЗаписатьНачалоЭлемента("Header");
ЗаписьXML.ЗаписатьНачалоЭлемента("message_id");
ЗаписьXML.ЗаписатьТекст("7180073272");
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента(); // Header
ЗаписьXML.ЗаписатьКонецЭлемента(); // SetStockRequest

ЗаписьXML.Закрыть();

---

Решение проблемы с BOM для устаревших версий платформы (1С 8.2)

Выясним, как решить задачу, если работа ведется на платформе 1С 8.2, где метод ОткрытьФайл не поддерживает отключение BOM. В этом случае мы можем записать XML стандартным образом, а затем пересохранить файл без BOM с помощью ADO-объекта ADODB.Stream, отсекающего первые три байта.

Разберем этот обходной путь по шагам:


// Шаг 1. Записываем обычный XML (платформа запишет его с BOM)
ИмяФайлаСBOM = ПолучитьИмяВременногоФайла("xml");
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяФайлаСBOM, "UTF-8");
ЗаписьXML.ЗаписатьОбъявлениеXML();
// ... запись узлов XML ...
ЗаписьXML.Закрыть();

// Шаг 2. Пересохраняем файл без BOM с помощью ADODB.Stream
ИмяФайлаБезBOM = ПолучитьИмяВременногоФайла("xml");

StreamВход = Новый COMОбъект("ADODB.Stream");
StreamВход.Type = 1; // Binary
StreamВход.Open();
StreamВход.LoadFromFile(ИмяФайлаСBOM);

// Пропускаем первые 3 байта (BOM)
StreamВход.Position = 3; 

StreamВыход = Новый COMОбъект("ADODB.Stream");
StreamВыход.Type = 1; // Binary
StreamВыход.Open();

// Копируем оставшиеся данные во второй поток
StreamВход.CopyTo(StreamВыход);
StreamВыход.SaveToFile(ИмяФайлаБезBOM, 2); // 2 = перезаписать файл

StreamВход.Close();
StreamВыход.Close();

// Теперь файл ИмяФайлаБезBOM готов к чистой отправке по HTTP

---

Отправка "сырого" XML (Raw Body) через HTTP-соединение

Очень часто разработчики усложняют интеграцию, пытаясь упаковать XML в multipart/form-data с разделителями boundary, в то время как принимающий веб-сервис ожидает обычный XML-документ методом POST. Если при тестировании в Postman или SoapUI вы передавали "чистый" XML в теле запроса и все работало, то и из 1С нужно отправлять данные аналогичным образом.

Давайте посмотрим на пример правильной отправки очищенного от BOM файла XML напрямую через HTTPСоединение:


// Определяем параметры подключения
Сервер = "rut123.ru";
Порт = 245;
Пользователь = "user";
Пароль = "123";

// Создаем HTTP-соединение
HTTPСоединение = Новый HTTPСоединение(Сервер, Порт, Пользователь, Пароль);

// Настраиваем заголовки запроса
Заголовки = Новый Соответствие();
// Указываем, что передаем чистый XML в кодировке UTF-8
Заголовки.Вставить("Content-Type", "application/xml; charset=utf-8");

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

Попытка
    // Отправляем файл XML (без BOM) напрямую на сервер методом POST
    HTTPСоединение.ОтправитьДляОбработки(ИмяФайлаБезBOM, "/Folder/Fold/Fol", ИмяФайлаОтвета, Заголовки);
    
    // Считываем ответ сервера
    ЧтениеТекста = Новый ЧтениеТекста(ИмяФайлаОтвета, КодировкаТекста.UTF8);
    ОтветСервера = ЧтениеТекста.Прочитать();
    ЧтениеТекста.Закрыть();
    
    Сообщить("Успешно! Ответ сервера: " + ОтветСервера);
Исключение
    Сообщить("Ошибка при отправке: " + ОписаниеОшибки());
КонецПопытки;

---

Как правильно сформировать Multipart/form-data (если это необходимо)

В случаях, когда спецификация API жестко требует отправку файла именно через multipart-форму, необходимо крайне внимательно собрать тело запроса. Основная сложность заключается в соблюдении переносов строк CRLF (в 1С это Символы.ПС или комбинация Символы.ВК + Символы.ПС в зависимости от требований операционной системы сервера).

Проанализируем правильную сборку multipart-файла:


Boundary = "Boundary_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
ИмяФайлаОтправки = ПолучитьИмяВременногоФайла("txt");

// Записываем текстовый заголовок multipart-запроса
ЗаписьТекста = Новый ЗаписьТекста(ИмяФайлаОтправки, КодировкаТекста.UTF8, Символы.ПС, Ложь);

// Начальная граница
ЗаписьТекста.ЗаписатьСтроку("--" + Boundary);
// Метаданные передаваемой части
ЗаписьТекста.ЗаписатьСтроку("Content-Disposition: form-data; name=""datafile""; filename=""Post.xml""");
// Важно: двоеточие после Content-Type и пустая строка перед телом XML!
ЗаписьТекста.ЗаписатьСтроку("Content-Type: application/xml");
ЗаписьТекста.ЗаписатьСтроку(""); // Это пустая строка, отделяющая заголовки от тела

ЗаписьТекста.Закрыть();

// Объединяем текстовый заголовок и чистый XML (без BOM)
МассивФайлов = Новый Массив;
МассивФайлов.Добавить(ИмяФайлаОтправки); // Заголовок multipart
МассивФайлов.Добавить(ИмяФайлаБезBOM);  // Тело XML без BOM

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

МассивФайлов.Добавить(ИмяФайлаЗакрытия);

// Склеиваем все части в итоговый файл отправки
ИтоговыйФайлЗапроса = КаталогВременныхФайлов() + "request_body.bin";
ОбъединитьФайлы(МассивФайлов, ИтоговыйФайлЗапроса);

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

// Рассчитываем и указываем размер контента
ФайлОтправки = Новый Файл(ИтоговыйФайлЗапроса);
ЗаголовкиHTTP.Вставить("Content-Length", XMLСтрока(ФайлОтправки.Размер()));

// Отправка через HTTPСоединение выполняется аналогично стандартному POST-запросу

---

Рекомендации по отладке интеграций

Если после выполнения всех инструкций веб-сервер продолжает возвращать ошибку, воспользуйтесь следующими практическими советами:

  1. Используйте прокси-отладчики (например, Fiddler). Настройте HTTP-соединение в 1С через локальный прокси-сервер (Fiddler) и перехватите отправляемый пакет. Это позволит вам увидеть запрос "байт в байт" и сравнить его с успешным запросом из Postman.
  2. Проверяйте кодировку в HEX-редакторе. Откройте сформированный в 1С файл XML в любом HEX-редакторе. Если первые три байта файла равны EF BB BF, значит, BOM всё ещё присутствует в файле, и его нужно вырезать.
← На главную