При интеграции 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С и реализуем правильные алгоритмы отправки данных.
По умолчанию при записи текстовых файлов или 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 этот параметр присутствует, но разработчики часто забывают его использовать.
Если веб-сервер принимает данные в формате multipart/form-data (передача файлов в теле запроса), разработчики часто собирают тело POST-запроса вручную, объединяя текстовые заголовки границ (boundary) и сам XML-файл. В этом процессе легко допустить синтаксические ошибки:
Content-typeapplication/xml вместо Content-Type: application/xml).CRLF) между заголовками multipart-части и началом самого XML-кода.--boundary--).multipart/form-data там, где сервер ожидает обычный "сырой" (raw) XML в теле запроса.---
Проанализируем самый простой и правильный способ записи 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.Закрыть();
---
Выясним, как решить задачу, если работа ведется на платформе 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 в 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);
ОтветСервера = ЧтениеТекста.Прочитать();
ЧтениеТекста.Закрыть();
Сообщить("Успешно! Ответ сервера: " + ОтветСервера);
Исключение
Сообщить("Ошибка при отправке: " + ОписаниеОшибки());
КонецПопытки;
---
В случаях, когда спецификация 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-запросу
---
Если после выполнения всех инструкций веб-сервер продолжает возвращать ошибку, воспользуйтесь следующими практическими советами:
EF BB BF, значит, BOM всё ещё присутствует в файле, и его нужно вырезать.