При интеграции платформы 1С с внешними веб-сервисами и REST API разработчики часто сталкиваются с ситуацией, когда запрос, успешно работавший в версии 1С:Предприятие 8.2 или при проверке сторонними утилитами, в 1С:Предприятие 8.3 возвращает ошибку 400 Bad Request со следующим текстом:
{"status":400,"error":"Invalid JSON request","message":"unexpected character at line 1, column 1"}
При этом сам текст документа полностью валиден и не содержит синтаксических ошибок. Разберем по шагам, почему возникает эта ошибка и какими способами можно правильно передать строку JSON без маркера последовательности байтов.
Обратим внимание на диагностическое сообщение веб-сервиса: парсер ожидает увидеть открывающую фигурную { или квадратную [ скобку, однако спотыкается на первом символе первой строки. Если сопоставить длину отправляемого пакета (заголовок Content-Length), можно заметить разницу ровно в 3 байта.
Причина такого поведения — добавление невидимой трехбайтовой сигнатуры UTF-8 Byte Order Mark (BOM: 0xEF, 0xBB, 0xBF) в самое начало тела запроса. Стандарт RFC 8259, определяющий формат JSON, запрещает наличие маркера BOM в начале потока данных. Большинство современных веб-серверов строго следуют этому правилу и отсекают запросы с маркером еще на этапе первичного синтаксического анализа.
Выясним причину появления BOM в 1С. В оперативной памяти строка, полученная через объект ЗаписьJSON, BOM не содержит. Сигнатура появляется в тот момент, когда мы помещаем сформированный текст в объект HTTPЗапрос. Метод HTTPЗапрос.УстановитьТелоИзСтроки() по умолчанию может автоматически добавлять маркер BOM для кодировки UTF-8 в зависимости от установленного режима совместимости конфигурации.
Рассмотрим самый простой, чистый и производительный вариант. Нам не требуется сохранять данные во временные файлы или очищать строку регулярными выражениями. Достаточно использовать штатные параметры платформы 1С:
// Формируем JSON в строку средствами платформы
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеДляОтправки);
СтрокаJSON = ЗаписьJSON.Закрыть();
// Формируем HTTP-запрос
HTTPЗапрос = Новый HTTPЗапрос("/api/v3/endpoint/");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
// Явно указываем ИспользованиеByteOrderMark.НеИспользовать
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
// Отправляем запрос через существующее HTTPСоединение
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Обратите внимание на третий параметр метода HTTPЗапрос.УстановитьТелоИзСтроки() — системное перечисление ИспользованиеByteOrderMark.НеИспользовать. Оно гарантирует, что строка попадет в сетевой сокет в чистом виде без стартовых трех байт.
Если в алгоритме требуется контролировать байтовое представление полезной нагрузки до отправки или использовать единые функции формирования запросов, разберем способ с преобразованием строки в двоичные данные без сигнатуры:
// Преобразуем строку в двоичные данные без добавления BOM
ЗаписатьBOM = Ложь;
ДвоичныеДанныеТела = ПолучитьДвоичныеДанныеИзСтроки(СтрокаJSON, КодировкаТекста.UTF8, ЗаписатьBOM);
// Устанавливаем двоичные данные напрямую в запрос
HTTPЗапрос = Новый HTTPЗапрос("/api/v3/endpoint/");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
HTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДвоичныеДанныеТела);
В методе ПолучитьДвоичныеДанныеИзСтроки() третий параметр принимает булево значение. Передавая Ложь, мы исключаем автоматическую вставку BOM.
Если сериализация объекта занимает значительный объем памяти и требуется прямая запись во временный файл на диске, обратимся к параметрам метода ОткрытьФайл() объекта ЗаписьJSON:
ИмяФайла = ПолучитьИмяВременногоФайла("json");
ЗаписьJSON = Новый ЗаписьJSON;
// Третий параметр метода отвечает за добавление BOM
ЗаписьJSON.ОткрытьФайл(ИмяФайла, "UTF-8", Ложь);
ЗаписатьJSON(ЗаписьJSON, ДанныеДляОтправки);
ЗаписьJSON.Закрыть();
HTTPЗапрос = Новый HTTPЗапрос("/api/v3/endpoint/");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
HTTPЗапрос.УстановитьТелоИзИмениФайла(ИмяФайла);
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
УдалитьФайлы(ИмяФайла);
Параметр Ложь при открытии файла гарантирует запись содержимого строго без маркера UTF-8 BOM.
Посмотрим на распространенные нюансы, с которыми можно столкнуться при решении данной проблемы:
Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8) не имеет флага отключения маркера BOM. Платформа 1С при создании файла на диске таким способом всегда добавляет маркер BOM в начало файла."CESU-8", так как для нее платформа 1С не ставит BOM. Однако этот метод опасен: кодировка CESU-8 отличается от UTF-8 при обработке символов за пределами базовой многоязыковой плоскости (например, эмодзи или специфические пиктограммы кодируются суррогатными парами). Это приведет к нарушению стандарта UTF-8 и ошибке парсинга на удаленном сервере. Рекомендуется всегда использовать КодировкаТекста.UTF8 с явным отключением BOM.