При интеграции 1С с внешними системами (особенно на базе Oracle, Java или PHP) разработчики часто сталкиваются с загадочной проблемой: запрос, который идеально работает в сторонних утилитах вроде Postman или Advanced Rest Client, выдает ошибку при отправке из 1С. Чаще всего принимающая сторона возвращает сообщение о сбое разбора данных, например: error ORA-31011: сбой разбора XML. При детальном анализе трафика выясняется, что в самом начале передаваемого текста появляется «паразитный» символ вопроса или точка.
В этой статье мы подробно разберем, откуда берется этот символ, почему стандартные методы записи файлов в 1С могут мешать обмену данными и как правильно сформировать HTTP-запрос, чтобы сервер его принял.
Разберем природу «паразитного символа». На самом деле это не знак вопроса, а BOM (Byte Order Mark) — специальная метка последовательности байтов. Для кодировки UTF-8 эта метка выглядит как последовательность трех байтов: EF BB BF.
Многие текстовые редакторы Windows и сама платформа 1С по умолчанию добавляют эти байты в начало текстового файла, чтобы приложения могли однозначно определить: перед ними текст в кодировке UTF-8. Однако стандарты веб-протоколов и многие строгие XML-парсеры (как в базе данных Oracle) не ожидают увидеть ничего, кроме самого XML-декларирования или корневого тега. Получив в начале потока данных EF BB BF, сервер интерпретирует их как некорректные символы и обрывает обработку с ошибкой разбора.
Рассмотрим типичный сценарий, с которого часто начинают разработчики 1С. Посмотрим на пример кода, который гарантированно приведет к появлению BOM:
Функция ВыполнитьPOSTЗапрос(ИмяСервера, Порт, Команда, СсылкаНаРесурс)
HTTP = Новый HTTPСоединение(ИмяСервера, Порт, , , , 60);
ФайлЗапроса = ПолучитьИмяВременногоФайла();
ТекстовыйФайл = Новый ТекстовыйДокумент;
ТекстовыйФайл.УстановитьТекст(СокрЛП(Команда));
// Ошибка здесь: метод Записать с этой кодировкой всегда добавит BOM
ТекстовыйФайл.Записать(ФайлЗапроса, КодировкаТекста.UTF8);
ФайлРезультата = ПолучитьИмяВременногоФайла();
ЗаголовокHTTP = Новый Соответствие();
ЗаголовокHTTP.Вставить("Content-Type", "application/xml; charset=utf-8");
HTTP.ОтправитьДляОбработки(ФайлЗапроса, СсылкаНаРесурс, ФайлРезультата, ЗаголовокHTTP);
// ... дальнейшая обработка
КонецФункции
В данном примере объект ТекстовыйДокумент при вызове метода Записать принудительно вставляет метку BOM в начало файла. Даже если вы не видите её в обычном блокноте, она физически присутствует в файле и улетает на сервер. Использование временных файлов для формирования тела запроса — это «карма», которая часто приводит к подобным ошибкам. Проанализируем более современные способы решения задачи.
Еще одна критическая ошибка при ручном формировании запроса — попытка самостоятельно вычислить и установить заголовок Content-Length. Рассмотрим, почему это опасно:
// ОШИБОЧНЫЙ ПУТЬ
ЗаголовокЗапросаHTTP.Вставить("Content-Length", СтрДлина(СтрокаОтправки));
Проанализируем ситуацию: функция СтрДлина возвращает количество символов в строке. Однако заголовок Content-Length должен содержать количество байтов. В кодировке UTF-8 один символ кириллицы занимает 2 байта. Если в вашей строке есть русские буквы, значение СтрДлина будет почти в два раза меньше реального размера данных в байтах. Сервер получит обрезанный текст и вернет ошибку 400 или 500.
Важное правило: никогда не устанавливайте Content-Length вручную. При использовании объекта HTTPЗапрос платформа 1С сделает это автоматически и корректно, вычислив размер данных после их преобразования в двоичный вид.
Теперь разберем, как правильно сформировать тело запроса без лишних байтов и файлов. Начиная с современных версий платформы 8.3, у метода УстановитьТелоИзСтроки появился третий параметр, управляющий BOM.
Рассмотрим итоговый, правильный вариант функции:
Функция ВыполнитьPOSTЗапрос(ИмяСервера, Порт, СтрокаОтправки, СсылкаНаРесурс) Экспорт
// 1. Создаем соединение без лишних параметров
HTTP = Новый HTTPСоединение(ИмяСервера, Порт, , , , 60);
// 2. Указываем заголовки. Content-Length НЕ ПИШЕМ!
ЗаголовокЗапросаHTTP = Новый Соответствие();
ЗаголовокЗапросаHTTP.Вставить("Content-Type", "application/xml; charset=utf-8");
// 3. Создаем объект запроса
HTTPЗапрос = Новый HTTPЗапрос(СсылкаНаРесурс, ЗаголовокЗапросаHTTP);
// 4. КЛЮЧЕВОЙ МОМЕНТ: Указываем ИспользованиеByteOrderMark.НеИспользовать
// Это гарантирует отсутствие "паразитного символа" в начале данных
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаОтправки, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
// 5. Отправляем запрос и получаем результат в одну строку
Попытка
Ответ = HTTP.ОтправитьДляОбработки(HTTPЗапрос);
Возврат Ответ.ПолучитьТелоКакСтроку();
Исключение
ЗаписьЖурналаРегистрации("Ошибка HTTP", УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
Если вы работаете на очень старой версии платформы, где у метода УстановитьТелоИзСтроки нет управления BOM, или хотите иметь 100% контроль над байтами, воспользуйтесь преобразованием в двоичные данные. Метод ПолучитьДвоичныеДанныеИзСтроки также позволяет явно отключить BOM.
Разберем этот алгоритм:
УстановитьТелоИзДвоичныхДанных.
// Пример для гарантированного удаления BOM на любых версиях
ТелоВБайтах = ПолучитьДвоичныеДанныеИзСтроки(СтрокаОтправки, КодировкаТекста.UTF8, Ложь);
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ТелоВБайтах);
Здесь третий параметр Ложь в функции ПолучитьДвоичныеДанныеИзСтроки как раз отвечает за то, что метка Byte Order Mark не будет добавлена в начало массива байтов.
Для успешной отправки XML или JSON из 1С во внешние системы придерживайтесь следующих рекомендаций:
ТекстовыйДокумент при формировании тела запроса.ИспользованиеByteOrderMark.НеИспользовать или Ложь в соответствующих методах.Content-Type: для XML это обычно application/xml, для JSON — application/json. Указание charset=utf-8 в заголовке желательно, но оно не заменяет физического удаления BOM из тела.Соблюдение этих правил позволит вам избежать большинства проблем с интеграцией и обеспечит корректную работу ваших обработок с любыми внешними веб-сервисами.