При интеграции 1С с Универсальным транспортным модулем (УТМ) ЕГАИС разработчики нередко сталкиваются с ошибкой сервера (HTTP 500) при отправке объемных накладных (WayBill). Анализ сетевого трафика через отладчик или анализатор пакетов показывает неожиданную картину: тело запроса отправляется не полностью, а обрезается примерно наполовину. При этом небольшие запросы (например, запрос остатков) уходят без каких-либо сбоев с кодом ответа 200.
Разберем детально, почему возникает такое поведение, как протокол HTTP интерпретирует размер передаваемых данных и как корректно составить запрос в платформе «1С:Предприятие».
Посмотрим на типичный фрагмент кода, приводящий к ошибке:
ЗаголовокHTTP = Новый Соответствие();
ЗаголовокHTTP.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
ЗаголовокHTTP.Вставить("Content-Length", XMLСтрока(СтрДлина(Body)));
ЗаголовокHTTP.Вставить("Connection", "Keep-alive");
ЗапросHTTP = Новый HTTPЗапрос("/opt/in/WayBill", ЗаголовокHTTP);
ЗапросHTTP.УстановитьТелоИзСтроки(Body, "UTF-8");
ОтветHTTP = НТТР.ОтправитьДляОбработки(ЗапросHTTP);
Обратим внимание на вычисление значения заголовка Content-Length. Здесь используется функция СтрДлина(Body), которая возвращает количество символов в строке.
По стандарту HTTP значение заголовка Content-Length определяет размер тела сущности исключительно в байтах (октетах), а не в символах. В кодировке UTF-8 символы кириллицы кодируются ровно двумя байтами. Если накладная содержит наименования товаров на русском языке, фактический размер строки в байтах оказывается практически в два раза больше, чем число символов. Веб-сервер УТМ ЕГАИС считывает из сокета ровно то количество байт, которое указано в Content-Length, и закрывает чтение. В результате XML-документ обрезается посередине, структура пакета нарушается, парсер не находит закрывающий boundary и возвращает ошибку 500 Internal Server Error.
Платформа 1С берет на себя управление низкоуровневыми сетевыми заголовками. При отправке запроса через методы объекта HTTPСоединение платформа самостоятельно и абсолютно точно рассчитывает размер сформированного тела в байтах и подставляет корректный заголовок в сетевой пакет.
Самое простое и надежное решение — полностью удалить ручную вставку заголовка Content-Length из коллекции заголовков:
ЗаголовокHTTP = Новый Соответствие();
ЗаголовокHTTP.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
ЗаголовокHTTP.Вставить("Connection", "Keep-alive");
ЗапросHTTP = Новый HTTPЗапрос("/opt/in/WayBill", ЗаголовокHTTP);
ЗапросHTTP.УстановитьТелоИзСтроки(Body, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
ОтветHTTP = НТТР.ОтправитьДляОбработки(ЗапросHTTP);
Если специфика внешнего шлюза или промежуточного прокси-сервера требует обязательного явного указания Content-Length до момента отправки, его необходимо рассчитывать в байтах. Для этого строку необходимо перевести в двоичные данные:
// Получаем двоичные данные строки в нужной кодировке
ДвоичныеДанныеТела = ПолучитьДвоичныеДанныеИзСтроки(Body, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
ЗаголовокHTTP = Новый Соответствие();
ЗаголовокHTTP.Вставить("Content-Type", "multipart/form-data; boundary=" + Boundary);
// Размер двоичных данных возвращает точный размер в байтах
ЗаголовокHTTP.Вставить("Content-Length", XMLСтрока(ДвоичныеДанныеТела.Размер()));
ЗапросHTTP = Новый HTTPЗапрос("/opt/in/WayBill", ЗаголовокHTTP);
ЗапросHTTP.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТела);
ОтветHTTP = НТТР.ОтправитьДляОбработки(ЗапросHTTP);
Если тело запроса сохраняется во временный файл на диске, размер определяется через объект Файл:
РазмерВБайтах = Новый Файл(ИмяФайлаТела).Размер();
ЗаголовокHTTP.Вставить("Content-Length", XMLСтрока(РазмерВБайтах));
Помимо корректного размера пакета, при взаимодействии с УТМ ЕГАИС следует учитывать следующие требования платформы и транспортного модуля:
УстановитьТелоИзСтроки() платформа может по умолчанию добавлять BOM-сигнатуру UTF-8 (байты EF BB BF). Встроенный в УТМ парсер XML не принимает BOM внутри multipart-содержимого и завершает разбор ошибкой. Всегда явно передавайте третий параметр: ИспользованиеByteOrderMark.НеИспользовать.CRLF (Символы.ВК + Символы.ПС) в качестве разделителя строк тела запроса. Использование одиночного Символы.ПС (характерно для серверов 1С под управлением Linux) приводит к ошибке 500 при разборе границ boundary. Заключительная граница должна иметь вид: "--" + Boundary + "--" + Символы.ВК + Символы.ПС./opt/in/... УТМ ожидает обязательный параметр с именем xml_file в заголовке части формы: Content-Disposition: form-data; name="xml_file"; filename="WayBill.xml".+ приводит к избыточному потреблению оперативной памяти. Рекомендуется записывать данные на диск с помощью объектов ЗаписьXML и ЗаписьТекста, а затем передавать файл через метод ЗапросHTTP.УстановитьИмяФайлаТела(ИмяФайла). Это обеспечивает потоковую передачу данных без загрузки файла целиком в память.HTTPСоединение рекомендуется устанавливать таймаут не менее 60–120 секунд, чтобы предотвратить обрыв соединения со стороны клиента.