При интеграции систем 1С с внешними веб-сервисами разработчики часто сталкиваются со сложностями настройки сетевых подключений. Особенно много нюансов возникает при ручной сборке XML-пакетов для SOAP-протокола и отправке их через низкоуровневые HTTP-запросы. В этой статье мы подробно разберем, почему возникают ошибки 404 Not Found и 500 Internal Server Error при отправке POST-запросов, проанализируем некорректные механизмы авторизации и рассмотрим наиболее правильные архитектурные решения для платформы 1С.
Рассмотрим типичную ошибку начинающих разработчиков при инициализации объектов HTTPСоединение и HTTPЗапрос. В исходном коде автора адрес обращения выглядел следующим образом:
Соединение = Новый HTTPСоединение("srs.gs1ru.org/GS46_Interfaces/GS1RU_Operations",,ИмяПользователя, Пароль);
HTTPЗапрос = Новый HTTPЗапрос("/GetDictionary", Заголовки);
Проанализируем эту ситуацию. Почему сервер возвращает ошибку 404, сообщая, что запрашиваемый ресурс не найден? Дело в том, что конструктор объекта HTTPСоединение в качестве первого параметра (ИмяСервера) принимает исключительно хост (доменное имя или IP-адрес) и, при необходимости, порт. Вся остальная часть пути к веб-сервису должна передаваться в конструктор объекта HTTPЗапрос.
Когда мы указываем весь путь в HTTPСоединение, платформа пытается установить TCP-соединение с некорректным адресом сервера, либо веб-сервер на стороне получателя не может правильно сопоставить пришедший HTTP-пакет с конкретным приложением (эндпоинтом), в результате чего возвращается стандартная ошибка 404.
Решение проблемы: разделим адрес на хост и путь к ресурсу. Оставим в соединении только имя сервера, а весь остальной путь перенесем в запрос. Перепишем этот участок кода правильно:
// В соединении указываем ТОЛЬКО хост
Соединение = Новый HTTPСоединение("srs.gs1ru.org",,,,, Новый ЗащищенноеСоединениеOpenSSL());
// В запросе указываем полный путь к ресурсу (веб-сервису)
HTTPЗапрос = Новый HTTPЗапрос("/GS46_Interfaces/GS1RU_Operations", Заголовки);
Обратите внимание, что мы также добавили параметр Новый ЗащищенноеСоединениеOpenSSL(). Современные веб-сервисы, включая ГС1 Рус (GS1 Russia), работают исключительно по защищенному протоколу HTTPS. Без явного указания объекта защищенного соединения попытка подключения завершится неудачей.
После исправления адресации разработчики часто натыкаются на следующую преграду — ошибку с кодом 500. В логах или ответе сервера при этом возвращается детальное описание проблемы:
Couldn't create SOAP message. Expecting Envelope in namespace http://schemas.xmlsoap.org/soap/envelope/, but got schemas.xmlsoap.org/soap/envelope/
Давайте разберем причину этого сбоя. Данное сообщение прямо указывает на то, что веб-сервер не смог распарсить пришедший SOAP-конверт. Веб-сервис ожидает строгое соответствие стандартам XML-разметки. В исходном шаблоне XML-документа была допущена критическая опечатка:
СтрокаSOAP="<soapenv:Envelope xmlns:soapenv=""schemas.xmlsoap.org/soap/envelope/"" ... >"
Обратите внимание на объявление пространства имен xmlns:soapenv. В строке пропущен префикс протокола http://. Для парсера XML-документов строка "schemas.xmlsoap.org/soap/envelope/" и строка "http://schemas.xmlsoap.org/soap/envelope/" — это абсолютно разные пространства имен. Веб-сервер не распознает корневой элемент SOAP-конверта и отвергает запрос, возвращая ошибку 500.
Решение проблемы: исправим опечатку в XML-шаблоне, добавив http:// в определение пространства имен:
СтрокаSOAP="<soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:urn=""urn:org.gs1ru.gs46.intf"">
|<soapenv:Header/>
|<soapenv:Body>
|<urn:GetDictionary>
|<urn:src>GS46NEW</urn:src>
|<urn:dictId>WEB_90001690</urn:dictId>
|<urn:lang>ru</urn:lang>
|<urn:noCache>0</urn:noCache>
|</urn:GetDictionary>
|</soapenv:Body>
|</soapenv:Envelope>";
После этого исправления сервер корректно примет и распарсит XML-пакет.
Давайте посмотрим на функцию получения заголовка авторизации, которую использовал автор:
Функция ПолучитьBase64ЗаголовокАвторизации(ИмяПользователя, Пароль)
КодировкаФайла = КодировкаТекста.UTF8;
ВременныйФайл = ПолучитьИмяВременногоФайла();
Запись = Новый ЗаписьТекста(ВременныйФайл, КодировкаФайла);
Запись.Записать(ИмяПользователя+":"+Пароль);
Запись.Закрыть();
ДвДанные = Новый ДвоичныеДанные(ВременныйФайл);
Результат = Base64Строка(ДвДанные);
УдалитьФайлы(ВременныйФайл);
Результат = Сред(Результат,5);
Возврат Результат;
КонецФункции
Этот код содержит много избыточных и потенциально опасных действий. Запись данных на жесткий диск во временный файл замедляет работу системы и создает лишнюю нагрузку на дисковую подсистему. Но самый главный «костыль» здесь — это строка Результат = Сред(Результат,5);.
Выясним причину ее появления. При записи текста в кодировке UTF-8 с помощью объекта ЗаписьТекста, платформа 1С по умолчанию добавляет в начало файла маркер последовательности байтов BOM (Byte Order Mark, байты EF BB BF). При преобразовании этих двоичных данных в строку Base64, маркер BOM превращается в лишние символы в начале закодированной строки. Автор пытался удалить их «вручную», обрезая первые 4 символа. Это крайне ненадежное решение, которое может привести к ошибкам авторизации при определенных значениях логина и пароля.
В современных версиях платформы 1С 8.3 всю эту операцию можно проделать мгновенно в оперативной памяти, используя метод ПолучитьДвоичныеДанныеИзСтроки. В качестве третьего параметра мы можем явно передать Ложь, чтобы отключить запись BOM-маркера.
Перепишем функцию по стандартам разработки:
Функция ПолучитьBase64ЗаголовокАвторизации(ИмяПользователя, Пароль)
СтрокаАвторизации = ИмяПользователя + ":" + Пароль;
// Третий параметр "Ложь" отключает запись BOM-символов
ДвДанные = ПолучитьДвоичныеДанныеИзСтроки(СтрокаАвторизации, КодировкаТекста.UTF8, Ложь);
Возврат Base64Строка(ДвДанные);
КонецФункции
Этот код работает в разы быстрее, не использует диск и гарантирует стопроцентно верный результат кодирования учетных данных.
Сборка SOAP-запросов вручную в текстовых строках — это трудоемкий процесс, подверженный синтаксическим ошибкам. Платформа 1С предлагает мощный встроенный механизм для работы с веб-сервисами, который называется WS-прокси.
Если у веб-сервиса есть WSDL-описание (а у сервиса GS1 Russia оно доступно по адресу https://srs.gs1ru.org/GS46_Interfaces/GS1RU_Operations?wsdl), мы можем переложить всю рутину по формированию XML-пакетов, прописыванию пространств имен и заголовков авторизации на саму платформу 1С.
Разберем по шагам, как выполнить тот же самый запрос GetDictionary через механизмы 1С:
// Шаг 1. Создаем объект определений веб-сервиса на основе WSDL-ссылки
// При создании определений передаем логин и пароль для авторизации на сервере WSDL
WSDL_Адрес = "https://srs.gs1ru.org/GS46_Interfaces/GS1RU_Operations?wsdl";
Определения = Новый WSОпределения(WSDL_Адрес, ИмяПользователя, Пароль);
// Шаг 2. Инициализируем WS-Прокси
ПространствоИмен = "urn:org.gs1ru.gs46.intf";
Прокси = Новый WSПрокси(Определения, ПространствоИмен, "GS1RU_Operations", "GS1RU_OperationsPort");
Прокси.Пользователь = ИмяПользователя;
Прокси.Пароль = Пароль;
// Шаг 3. Вызываем метод GetDictionary напрямую как стандартную функцию 1С!
// Передаем параметры: src, dictId, lang, noCache
Попытка
Результат = Прокси.GetDictionary("GS46NEW", "WEB_90001690", "ru", 0);
// Обрабатываем полученный результат (объект XDTO или структура данных)
// ...
Исключение
Сообщить("Ошибка при вызове веб-сервиса: " + ОписаниеОшибки());
КонецПопытки
Использование WSПрокси дает нам множество преимуществ:
Пользователь и Пароль у объекта WSПрокси, вы избавляете себя от необходимости вручную кодировать Base64 и прописывать HTTP-заголовки.Таким образом, мы рассмотрели ключевые проблемы ручной отправки запросов SOAP и показали, как перевести интеграцию на надежные рельсы использования встроенных инструментов платформы 1С 8.3.