При разработке интеграций между базами 1С через HTTP-сервисы разработчики нередко сталкиваются с ситуацией: на стороне клиента в объект HTTPЗапрос явно помещаются данные с помощью метода УстановитьТелоИзСтроки(), однако на стороне сервиса-приемника вызов Запрос.ПолучитьТелоКакСтроку() возвращает абсолютно пустую строку. Проанализируем, почему возникает такое поведение платформы и веб-сервера, а также разберем по шагам варианты решения проблемы.
Посмотрим на типовой ошибочный код отправки данных со стороны вызывающей конфигурации:
Соединение = Новый HTTPСоединение("server.domain.local", , "user", "password");
Запрос = Новый HTTPЗапрос("QueryInsurance");
Запрос.УстановитьТелоИзСтроки("11");
Ответ = Соединение.Получить(Запрос);
На стороне конфигурации-приемника в методе HTTP-сервиса выполняется чтение:
Функция ШаблонURLПолучитьДанныеОСтраховании(Запрос)
УстановитьПривилегированныйРежим(Истина);
ТелоЗапроса = Запрос.ПолучитьТелоКакСтроку(); // Возвращает пустую строку!
...
КонецФункции
Выясним причину такого поведения. Метод платформы Соединение.Получить() выполняет стандартный HTTP-запрос методом GET. Согласно спецификациям протокола HTTP, семантика передачи тела в запросах с методом GET не определена и не предполагает передачу полезной нагрузки (payload).
Большинство современных веб-серверов (Apache HTTP Server, IIS, Nginx), через которые публикуются базы 1С, а также промежуточные прокси-серверы при обработке метода GET просто отсекают тело сообщения и заголовок Content-Length. Модуль расширения веб-сервера 1С принимает запрос уже без содержимого, поэтому метод Запрос.ПолучитьТелоКакСтроку() возвращает пустую строку. Даже если объект HTTPЗапрос в исходной базе содержал строку, до логики конфигурации-приемника эти байты физически не доходят.
Если для выполнения запроса требуется передать произвольное тело (строку, JSON, XML), правильным архитектурным решением является смена HTTP-метода с GET на POST. Разберем по шагам, какие изменения необходимо внести в обеих базах.
В дереве метаданных найдем наш HTTP-сервис, раскроем нужный Шаблон URL. Если ранее там был создан только метод GET, добавим подчиненный метод POST (или заменим существующий), назначив ему имя и функцию-обработчик в модуле сервиса. Если этот шаг пропустить, вызывающая сторона при отправке данных получит ошибку 405 Method Not Allowed.
Вместо метода Соединение.Получить() вызовем метод Соединение.ОтправитьДляОбработки(). Также хорошей практикой является явное указание заголовка типа контента и параметров кодировки без использования BOM (Byte Order Mark):
Соединение = Новый HTTPСоединение("server.domain.local", , "user", "password");
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "text/plain; charset=utf-8");
Запрос = Новый HTTPЗапрос("QueryInsurance", Заголовки);
Запрос.УстановитьТелоИзСтроки("11", КодировкаТекста.UTF8, ИспользованиеBOM.НеИспользовать);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Теперь функция-обработчик метода POST гарантированно прочитает переданную строку:
Функция ШаблонURLQueryInsurancePOST(Запрос)
УстановитьПривилегированныйРежим(Истина);
ТелоСтрокой = Запрос.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
// Формируем ответ клиенту
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.УстановитьТелоИзСтроки("{""status"": ""ok""}");
Возврат Ответ;
КонецФункции
Если по условиям задачи метод запроса должен оставаться строго GET (например, операция является идемпотентной выборкой данных без изменения состояния системы), передавать информацию в теле запроса нельзя. В этом случае данные передаются через параметры адресной строки (query parameters).
Рассмотрим, как сформировать запрос с параметрами на стороне клиента:
// Передаем параметр прямо в относительный URL
ИмяПараметра = "insuranceId";
ЗначениеПараметра = "11";
ОтносительныйURL = "QueryInsurance?" + ИмяПараметра + "=" + ТекстДляURL(ЗначениеПараметра);
Запрос = Новый HTTPЗапрос(ОтносительныйURL);
Ответ = Соединение.Получить(Запрос);
На стороне HTTP-сервиса в конфигурации 1С переданные параметры считываются через свойство ПараметрыЗапроса объекта HTTPСервисЗапрос:
Функция ШаблонURLПолучитьДанныеОСтраховании(Запрос)
УстановитьПривилегированныйРежим(Истина);
ЗначениеИД = "";
Если Запрос.ПараметрыЗапроса.Свойство("insuranceId", ЗначениеИД) Тогда
// Обработка полученного идентификатора "11"
КонецЕсли;
Ответ = Новый HTTPСервисОтвет(200);
Возврат Ответ;
КонецФункции
Если объем передаваемых данных невелик (токены, простые числовые коды, идентификаторы сессий), а URL перегружать параметрами нежелательно, можно передать данные в заголовках HTTP-пакета. Заголовки сохраняются веб-сервером как при GET, так и при любых других методах.
Пример отправки через заголовки:
Заголовки = Новый Соответствие;
Заголовки.Вставить("X-Insurance-ID", "11");
Запрос = Новый HTTPЗапрос("QueryInsurance", Заголовки);
Ответ = Соединение.Получить(Запрос);
Чтение заголовка на стороне сервиса:
Функция ШаблонURLПолучитьДанныеОСтраховании(Запрос)
ЗначениеИД = Запрос.Заголовки.Получить("X-Insurance-ID");
// Обработка данных
Возврат Новый HTTPСервисОтвет(200);
КонецФункции
Если вы уже перешли на метод POST, но тело всё равно оказывается пустым, разберем ключевые шаги для проверки:
Запрос.ПолучитьТелоКакДвоичныеДанные(). Если метод возвращает объект размером 0 байт, тело было потеряно веб-сервером до входа в платформу. Если размер больше нуля, проблема кроется в несовпадении кодировок при вызове ПолучитьТелоКакСтроку().Запрос.ПолучитьТелоКакПоток() в общих модулях логирования или подсистемах аудита до вашего кода. Поток может быть уже прочитан до конца, из-за чего повторная попытка вернет пустоту.Новый HTTPСоединение("host") должен содержать только имя сервера (хост) и порт без указания протоколов (http:// или https://) и без путей публикации. Пути публикации указываются в конструкторе HTTPЗапрос.