При интеграции мобильных приложений и сторонних веб-сервисов с базой 1С через модуль HTTPСервис часто возникает ситуация, когда данные передаются не в виде чистого JSON-документа в теле запроса (application/json), а упаковываются в виде классических полей формы (application/x-www-form-urlencoded). В этом случае в теле запроса приходит строка вида report_data={...JSON...}&device_id=123, где параметры могут следовать в произвольном порядке, а значения закодированы спецсимволами URL-encoding.
Разберем типичные ошибки, выясним причину сбоев при попытке сразу декодировать строку и построим универсальный алгоритм для корректного разбора параметров и десериализации JSON.
Проанализируем распространенную ошибку: если применить функцию РаскодироватьСтроку() сразу ко всему телу запроса, полученному через Запрос.ПолучитьТелоКакСтроку(), то все закодированные разделители (например, символы =, & или фигурные скобки внутри строковых полей JSON) декодируются одновременно. В результате строка превращается в смесь служебных разделителей формы и содержимого вложенного JSON. Попытка разделить такую строку на части по символам & или = приведет к искажению данных, а передача всей строки в ПрочитатьJSON() вызовет исключение и аварийное завершение работы сервиса с кодом 500 Internal Server Error.
Также важно помнить, что встроенное соответствие ПараметрыЗапроса объекта HTTPСервисЗапрос заполняется платформой только для параметров, переданных в строке URL (URI Query String). Если же данные отправлены методом POST или PUT в теле запроса, платформа 1С не разбирает тело автоматически — парсинг необходимо выполнять в коде сервиса.
Разберем по шагам строгую последовательность действий, соответствующую стандартам RFC:
&. Так как значения параметров внутри тела еще закодированы (URL-encoded), служебные амперсанды внутри значений не нарушат разделение.=). Все, что слева от него — имя параметра, а все, что справа — его закодированное значение.РаскодироватьСтроку() с параметром СпособКодированияСтроки.КодировкаURL только к значению конкретного параметра.+. Рекомендуется заменить + на стандартный пробел перед дальнейшей обработкой.ЧтениеJSON и десериализуем через ПрочитатьJSON().Посмотрим на пример универсальной функции, которая преобразует URL-encoded тело запроса в удобную Структура или Соответствие:
Функция РазобратьПараметрыТела(Знач ТелоСтрока) Экспорт
Результат = Новый Структура;
Если ПустаяСтрока(ТелоСтрока) Тогда
Возврат Результат;
КонецЕсли;
// Разбиваем тело на отдельные параметры по знаку &
МассивПараметров = СтрРазделить(ТелоСтрока, "&", Ложь);
Для Каждого СтрПараметр Из МассивПараметров Цикл
ПозицияРавно = СтрНайти(СтрПараметр, "=");
Если ПозицияРавно = 0 Тогда
// Параметр без значения или флаговый параметр
ИмяПараметра = СтрПараметр;
ЗначениеСырое = "";
Иначе
ИмяПараметра = Лев(СтрПараметр, ПозицияРавно - 1);
ЗначениеСырое = Сред(СтрПараметр, ПозицияРавно + 1);
КонецЕсли;
// Заменяем знак '+' на пробел перед URL-декодированием
ЗначениеСырое = СтрЗаменить(ЗначениеСырое, "+", " ");
// Раскодируем только значение параметра
ЗначениеПараметра = РаскодироватьСтроку(ЗначениеСырое, СпособКодированияСтроки.КодировкаURL);
// Сохраняем параметр в структуру
Результат.Вставить(ИмяПараметра, ЗначениеПараметра);
КонецЦикла;
Возврат Результат;
КонецФункции
Теперь рассмотрим, как применить созданную функцию непосредственно в методе-обработчике HTTP-сервиса. Важно обернуть чтение JSON в блок Попытка ... Исключение, чтобы в случае некорректного синтаксиса возвращать клиенту статус 400 Bad Request с понятным описанием, а не падать с кодом 500.
Функция ОтчетыPOST(Запрос)
// 1. Считываем тело запроса
ТелоСтрока = Запрос.ПолучитьТелоКакСтроку();
// 2. Разбираем параметры формы
ПараметрыТела = РазобратьПараметрыТела(ТелоСтрока);
// Проверяем наличие обязательного параметра с JSON-данными
Если НЕ ПараметрыТела.Свойство("report_data") Тогда
Ответ = Новый HTTPСервисОтвет(400);
Ответ.УстановитьТелоИзСтроки("В теле запроса отсутствует обязательный параметр 'report_data'");
Возврат Ответ;
КонецЕсли;
СтрокаJSON = ПараметрыТела.report_data;
// 3. Десериализуем JSON
Чтение = Новый ЧтениеJSON;
Попытка
Чтение.УстановитьСтроку(СтрокаJSON);
ДанныеОтчета = ПрочитатьJSON(Чтение);
Чтение.Закрыть();
Исключение
Чтение.Закрыть();
Ответ = Новый HTTPСервисОтвет(400);
Ответ.УстановитьТелоИзСтроки("Ошибка парсинга JSON: " + ОписаниеОшибки());
Возврат Ответ;
КонецПопытки;
// 4. Получаем дополнительные параметры (если они переданы)
ИдентификаторУстройства = "";
Если ПараметрыТела.Свойство("device_id") Тогда
ИдентификаторУстройства = ПараметрыТела.device_id;
КонецЕсли;
// 5. Выполняем прикладную обработку полученных данных
// Например: ОбработатьДанныеОтчета(ДанныеОтчета, ИдентификаторУстройства);
// 6. Формируем успешный ответ клиенту
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.УстановитьТелоИзСтроки("{""status"": ""success""}");
Возврат Ответ;
КонецФункции
Разберем несколько важных нюансов, которые помогут избежать ошибок при интеграции:
Запрос.ПолучитьТелоКакСтроку() по умолчанию считывает данные в кодировке UTF-8. Если внешний источник отправляет данные в другой кодировке (например, Windows-1251), укажите её явным параметром при вызове метода.ПрочитатьJSON() нежелательна, можно использовать пошаговый обход узлов через методы Чтение.Прочитать() и проверку Чтение.ТипУзла.device_id придет раньше report_data) или появление новых параметров не сломает работу HTTP-сервиса.