Как исправить обрезку XML-данных (SOAP/HTTP) при отправке запросов из 1С?

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

При интеграции систем 1С с внешними веб-сервисами по протоколу SOAP или посредством обычных HTTP-запросов разработчики иногда сталкиваются с крайне загадочным поведением: отправляемый XML-пакет доходит до принимающей стороны «обрезанным» на несколько символов в самом конце. Например, вместо закрывающего тега </SOAP-ENV:Envelope> сервер получает только </SOAP-ENV:Envelo, что делает весь XML-документ невалидным и приводит к ошибкам разбора (парсинга) на стороне сервера.

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

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

Проанализируем ситуацию, с которой столкнулся автор темы. Для отправки данных использовался следующий код:

РезультатСоединения = Соединение.ОтправитьДляОбработки(Запрос);

При этом в заголовки запроса вручную устанавливалось значение Content-Length следующим образом:

ЗаголовокHTTP.Вставить("Content-Length", XMLСтрока(СтрДлина(XMLпакет)));

В этой строке кода и кроется главная методическая ошибка. Функция СтрДлина() в 1С вычисляет количество символов в текстовой строке. Однако спецификация протокола HTTP строго требует, чтобы заголовок Content-Length указывал размер тела запроса исключительно в байтах (октетах), а не в символах.

Давайте разберем по шагам, почему при отправке строки в кодировке UTF-8 возникла нехватка ровно 3 байт (символов pe> на конце пакета):

  1. Влияние сигнатуры BOM (Byte Order Mark): При записи строки в поток UTF-8 платформа 1С по умолчанию может добавлять в начало файла или потока специальную невидимую метку порядка байтов (BOM). В кодировке UTF-8 эта метка состоит из трех конкретных байтов: EF BB BF.
  2. Разница между символами и байтами: Функция СтрДлина() считает только видимые текстовые символы внутри переменной 1С. Она ничего не знает о том, что при конвертации строки в двоичный поток для отправки по сети платформа добавит в начало 3 служебных байта BOM.
  3. Реакция веб-сервера: Когда вы вручную передаете Content-Length, равный длине строки в символах, вы дезинформируете принимающий сервер. Например, длина строки составляет 1000 символов, и вы передаете Content-Length: 1000. Но реальный отправленный поток байтов за счет BOM равен 1003 байтам. Веб-сервер, строго следуя спецификации HTTP, считывает из сетевого сокета ровно 1000 байт (начиная с 3 байт BOM) и прекращает чтение. В итоге последние 3 байта полезной нагрузки (символы p, e и >) просто отбрасываются сервером.

Проблема национальных алфавитов (кириллицы) в UTF-8

Ситуация с ручным подсчетом длины через СтрДлина() становится еще более критичной, если в SOAP-запросе передаются русские буквы или спецсимволы. В кодировке UTF-8 символы латиницы и знаки препинания занимают 1 байт, но каждый символ кириллицы занимает уже 2 байта (а некоторые сложные символы — до 4 байт).

Если ваша строка содержит, например, 500 русских символов, то в байтах этот текст будет занимать 1000 байт. Если вы передадите длину через СтрДлина(), сервер обрежет ровно половину вашего сообщения. Поэтому ручной подсчет длины строки в символах для заголовка Content-Length категорически недопустим.

---

Способ 1. Полное удаление ручной установки заголовка Content-Length (Рекомендуемый)

Самое простое, красивое и правильное решение — вообще не трогать заголовок Content-Length. Современная платформа 1С:Предприятие 8 спроектирована так, чтобы максимально облегчить жизнь разработчику.

При использовании объекта HTTPСоединение и вызове методов отправки (таких как ОтправитьДляОбработки(), ВызватьHTTPМетод()) платформа 1С самостоятельно анализирует тело запроса, переводит его в двоичный формат, учитывает кодировку, наличие BOM и автоматически генерирует корректный заголовок Content-Length в байтах непосредственно перед отправкой пакета в сеть.

Посмотрим на пример правильного формирования запроса:


// Создаем соединение
Соединение = Новый HTTPСоединение("api.service.ru", , , , , 30);

// Создаем HTTP-запрос
Запрос = Новый HTTPЗапрос("/soap/endpoint");

// Устанавливаем только тип контента (Content-Type)
Запрос.Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");

// Устанавливаем тело запроса напрямую из строки
Запрос.УстановитьТелоИзСтроки(XMLСтрокаДанных, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

// Отправляем запрос. Платформа сама рассчитает и добавит Content-Length!
Ответ = Соединение.ОтправитьДляОбработки(Запрос);

---

Способ 2. Отключение BOM при формировании тела запроса

Большинство современных веб-серверов, разработанных на PHP, Java, Python, Node.js или Go, не ожидают увидеть сигнатуру BOM в начале HTTP-запроса. Наличие BOM часто приводит к сбоям xml-парсеров на стороне сервера (например, к популярной ошибке "Content is not allowed in prolog").

При установке текстового тела в объект HTTPЗапрос рекомендуется явно отключать запись BOM третьим параметром метода УстановитьТелоИзСтроки():


Запрос.УстановитьТелоИзСтроки(
    ТекстXML, 
    КодировкаТекста.UTF8, 
    ИспользованиеByteOrderMark.НеИспользовать
);

Это гарантирует, что в начале вашего XML-пакета не будет никаких скрытых мусорных байтов, и принимающая сторона корректно распознает структуру документа с самого первого символа <.

---

Способ 3. Как правильно рассчитать длину тела в байтах вручную

Рассмотрим подробнее ситуацию, когда вам по какой-то причине (например, при интеграции со старыми нестандартными системами, использовании COM-объектов или специфических требований API) все же необходимо жестко зафиксировать заголовок Content-Length вручную.

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


// 1. Преобразуем исходную строку XML в двоичные данные (в кодировке UTF-8 без BOM)
ДвоичныеДанныеТела = ПолучитьДвоичныеДанныеИзСтроки(XMLСтрокаДанных, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

// 2. Получаем точный размер двоичных данных в байтах
РазмерВБайтах = ДвоичныеДанныеТела.Размер();

// 3. Преобразуем число в строковый формат без разделителей триад (пробелов)
СтрокаРазмера = XMLСтрока(РазмерВБайтах);

// 4. Устанавливаем заголовок Content-Length
Запрос.Заголовки.Вставить("Content-Length", СтрокаРазмера);
Запрос.Заголовки.Вставить("Content-Type", "text/xml; charset=utf-8");

// 5. Устанавливаем тело запроса из подготовленных двоичных данных
Запрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТела);

Важное примечание: Обратите внимание на использование функции XMLСтрока() при преобразовании размера файла в строку. Если использовать стандартную функцию Строка(РазмерВБайтах), то для больших пакетов (например, размером более 1000 байт) 1С вернет строку с разделителем групп разрядов (например, "1 024" вместо "1024"). Наличие пробела в заголовке Content-Length гарантированно приведет к ошибке разбора HTTP-заголовков на веб-сервере.

---

Резюме и лучшие практики

Для того чтобы избежать мистических ошибок с обрезкой XML-пакетов при работе с HTTP/SOAP в 1С, придерживайтесь следующих простых правил:

← На главную