Прокси soap-сервер. Когда 1С не может в SOAP
Разработка - Практика программирования
Вступление
Кто-то может сказать: "Ой, да что там руками формировать, XML простая". Однако протокол содержит некоторое количество расширений, таких как, например, WS-Addressing и WS-Security, которые могут превратить ручное формирование в боль.
На работе мне пришлось столкнуться с довольно замороченным soap-сервером, с которым не получалось легко работать из 1С. Сегодняшняя моя статья про то, как можно разработать легковесную прослойку между 1С и soap-сервером, принимающую в себя обычный http-запрос и перекладывающую содержимое в вызов soap-сервера. Естественно, код в статье максимально упрощен для простоты восприятия. Код работающего у меня production-решения сильно отличается от указанного примера и более «архитектурный» :).
Подготовка
В качестве инструмента для решения задачи я буду использовать node.js. Почему? Во-первых, мне так удобнее: я его знаю :). Во-вторых, на нем есть простые для запуска библиотеки для построения веб-приложений, работы с soap и кластеризацией. В качестве редактора я рекомендую использовать Visual Studio Code, но это уже дело вкуса. Тренироваться будем на классическом сервисе курсов валют.
После установки node.js в командной строке вам должна быть доступа утилита npm - пакетный менеджер для node.js. Для работавших с opm - это почти тоже самое, только для node.js и мощнее :).
Начнем разработку в пустом каталоге. Для первичной инициализации проекта нужно выполнить npm init - эта команда задаст манифест приложения с необходимыми полями. В целом, на все вопросы можно ответить значением по умолчанию.
После сразу установим все библиотеки, которые нам понадобятся для нашей прослойки с помощью команды:
npm install --save soap body-parser express
Файл с wsdl положим в корень каталога с именем DailyInfo.wsdl в кодировке UTF-8.
Для достижения нашей цели нам надо решить следующие задачи:
-
Написать веб-сервер, который сможет принимать POST запросы (это совсем не так сложно, как звучит).
-
Подключиться к soap-серверу как клиент.
-
Преобразовать входящий POST-запрос в вызов soap-метода и вернуть на клиент результат.
Страшно? 10 минут, помните?
Реализация – веб-сервер
Создадим скелет нашего приложения - файл index.js в корневом каталоге (если вы не указывали иное при выполнении npm init) со следующим содержимым:
// express – фреймворк для построения веб-приложений
const express = require('express');
// Преобразователь тела сообщения к объекту JavaScript. Мы его будем
// использовать для автопреобразования сообщения с Content-Type
// application/json из собственно JSON в объект.
const bodyParser = require("body-parser");
// Объявление главной функции. Async-возможность нам понадобится чуть позднее.
async function main() {
// Порт, который будет слушать веб-сервер
const port = 3000;
// Создание экземпляра веб-приложения
const app = express();
// Указание реагировать на POST-запрос
app.post(
"/", // по «пустому» ресурсу
bodyParser.json(), // с автоматическим преобразованием json-содержимого
(req, res) => { // и выводом Привет, мир :)
res.send("Hello, World");
}
);
// Запуск приложения – указание слушать порт
// и выводить сообщение в консоль по готовности
app.listen(port, () => console.log(`Test app listening on port ${port}!`));
}
// Точка входа
main();
Этим небольшим скриптом мы сразу же решили задачу №1 из нашего списка. Осталось запустить и проверить.
Для запуска приложения у нас есть два варианта:
-
запуск из командной строки через node index.js;
-
запуск отладчика в VSCode.
С первым вариантом все просто: вбили в консоль и радуемся:
Останавливаем работу через Ctrl-C.
Отладчик VSCode запускается по кнопке F5. В выпадающем меню надо выбрать Node.js:
После выбора node.js на вкладке Debug console можно убедиться, что наше приложение запустилось и готово обрабатывать запросы:
Для проверки работоспособности я воспользуюсь чудесным инструментом отладки http-запросов Postman:
В ответе сервиса видим, что он не может обработать GET-запрос, что логично. Поменяем запрос на POST и получим уже ожидаемый ответ:
Реализация – soap-клиент
Перейдем ко второй части – подключение по soap-серверу в качестве клиента. Для этого в уже существующий файл нужно добавить два участка кода. В секцию подключения библиотек добавим подключение “soap” – библиотеки, с помощью которой можно как подключиться к чужому soap-серверу, так и опубликовать собственный.
const soap = require("soap");
Внутрь функции main добавим создание soap-клиента:
// создание soap-клиента на базе предварительно скачанной wsdl.
// В качестве параметра может выступать как адрес к файлу на диске,
// так и URL, по которому этот WSDL можно получить (прямо как WS-Ссылка)
const soapClient = await soap.createClientAsync("./DailyInfo.wsdl");
soapClient.setEndpoint("http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx");
Иии… всё. Теперь через созданного клиента мы можем вызывать методы soap-сервера, как указанные с «полным» именем в виде soapClient,service.port.methodName(), так и по короткому soapClient.methodName().
Реализация – Преобразование запроса
Добавим, собственно, вызов нужного нам soap-метода. В качестве API нашего сервиса предлагаю такую простую схему: в теле POST-запроса передается JSON следующей структуры:
-
method – строка – имя вызываемого soap-метода;
-
body – произвольный – тело soap-запроса в виде js-объекта.
Таким образом, получение списка валют на определенную дату через наш промежуточный сервис может выглядеть так:
{
"method": "GetCursOnDate",
"body": { "On_date": "2018-01-01" }
}
Заменим наш ответ «привет, мир» на следующий код:
// Десериализованное тело запроса доступно в переменной req.body
// В случае корректного запроса req.body будет содержать два свойства:
// method и body
// Попробуем получить указатель на функцию для вызова soap-метода
const soapMethod = soapClient[req.body.method];
// Если метод не нашелся, выбросим исключение
if (soapMethod == undefined) {
throw new Error("Wrong method name");
}
// Если все хорошо, вызовем soap-метод, передав ему в качестве параметров
// тело сообщения и обработчик результата вызова
soapMethod(req.body.body, (err, result) => {
// В случае возникновения ошибки вернем ее клиенту.
if (err) {
res.send(err);
return;
}
// Если все хорошо, переведем ответ в JSON и вернем клиенту.
res.send(JSON.stringify(result));
});
Кода меньше, чем комментариев :). Сохраняемся, перезапускаемся и снова идем в Postman. На вкладке body укажем, что мы отправляем raw-данные с типом application/json и содержимым из примера выше:
В результате видим тело soap-ответа в виде JSON.
Реализация – вызов из 1С
Postman – это хорошо, но мы же изначально пришли с проблемой вызова из 1С. Выполнить обычный POST-запрос из 1С не составит труда, однако, я приведу пример реализации здесь, чтобы показать работу с JSON и XDTO.
Для начала добавим пакет XDTO в конфигурацию 1С. Если WSDL от поставщика soap-сервера читается, можно сразу добавить WS-ссылку. Сэмулируем проблему "нечитабельности" wsdl и добавим XDTO пакет вручную. В этом нам поможет знание о том, что WSDL содержит XSD для содержимого всех сообщений и методов.
Вытащим из WSDL все содержимое тега s:schema в отдельный файл и перенесем объявление пространства имен s из заголовка WSDL в заголовок нового файла. Получится что-то вроде такого:
Сохраним содержимое в файл с разрешением XSD, и, если все прошло успешно, полученная схема успешно импортируется в конфигуратор как XDTO пакет:
Если от вендора пришла «нечитаемая» в 1С XSD, то использование фабрики XDTO из следующего примера не имеет смысла, однако десериализацию из JSON будет просто написать по аналогии с сериализацией.
Для формирования запросов создадим внешнюю обработку со следующим кодом:
&НаСервереБезКонтекста
Процедура ВыполнитьЗапросНаСервере()
// Создадим тело нашего запроса - параметры вызываемого soap-метода
ТелоЗапроса = Новый Структура;
ТелоЗапроса.Вставить("On_date", Дата(2018, 1, 1));
// Сериализуем его в JSON
ТекстСообщения = СериализоватьВJSON(ТелоЗапроса);
// Проверим, что полученный JSON удовлетворяет XSD
// Если бы у данного свойства был бы выделенный "тип объекта",
// то мы бы могли получить тип проще...
//ТипXDTO = ФабрикаXDTO.Тип(Метаданные.ПакетыXDTO.ПакетXDTO1.ПространствоИмен, "GetCursOnDate");
КорневыеСвойства = ФабрикаXDTO.Пакеты.Получить(Метаданные.ПакетыXDTO.ПакетXDTO1.ПространствоИмен).КорневыеСвойства;
СвойствоЗапросКурсовНаДату = КорневыеСвойства.Получить("GetCursOnDate");
ТипXDTO = СвойствоЗапросКурсовНаДату.Тип;
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ТекстСообщения);
// Если здесь не выдалось исключения, значит, пакет корректен и его можно отправлять.
ФабрикаXDTO.ПрочитатьJSON(ЧтениеJSON, ТипXDTO);
// Создадим верхнеуровневую структуру, принимаемую промежуточным сервером
СтруктураЗапроса = Новый Структура;
СтруктураЗапроса.Вставить("method", "GetCursOnDate");
СтруктураЗапроса.Вставить("body", ТелоЗапроса);
// Сериализуем его в JSON для последующей отправки
ТекстСообщения = СериализоватьВJSON(СтруктураЗапроса);
// Создадим новое соединение с промежуточным сервером
Хост = "localhost";
Порт = 3000;
Таймаут = 30;
Соединение = Новый HTTPСоединение(Хост, Порт, , , , Таймаут);
// "Корневой" адрес ресурса, как мы его объявили в app.post
Ресурс = "/";
// Обязательно передаем тип содержимого для работы преобразователя body-parser
ЗаголовкиЗапроса = Новый Соответствие();
ЗаголовкиЗапроса.Вставить("Content-type", "application/json");
// Создаем и отправляем запрос
Запрос = Новый HTTPЗапрос(Ресурс, ЗаголовкиЗапроса);
Запрос.УстановитьТелоИзСтроки(ТекстСообщения);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
ТелоОтвета = Ответ.ПолучитьТелоКакСтроку();
// Десериализуем ответ сервиса из JSON в объект XDTO
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ТелоОтвета);
ТипXDTO = ФабрикаXDTO.Тип(Метаданные.ПакетыXDTO.ПакетXDTO1.ПространствоИмен, "GetCursOnDateResponse");
Данные = ФабрикаXDTO.ПрочитатьJSON(ЧтениеJSON, ТипXDTO);
КонецПроцедуры
&НаСервереБезКонтекста
Функция СериализоватьВJSON(Объект)
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Объект);
ТекстСообщения = Запись.Закрыть();
Возврат ТекстСообщения;
КонецФункции
&НаКлиенте
Процедура ВыполнитьЗапрос(Команда)
ВыполнитьЗапросНаСервере();
КонецПроцедуры
Некоторые пояснения по коду.
Первое, за что может зацепиться взгляд – использование «обычной» ЗаписиJSON вместо ФабрикиXDTO. Причина тут довольно проста – ФабрикаXDTO умеет записывать произвольные типы, но с «мусорными» тегами “#value” и “#type” (тип добавляется, только если это указано явно в настройках записи). Наш промежуточный сервер ни про какие value ничего не знает. Выхода тут два – либо научить понимать сервер, либо использовать упрощенный сериализатор на базе структуры и записи. Выбор за вами.
Второе – пляски с бубном вокруг ФабрикиXDTO. Это всего лишь проверка валидности нашего сообщения. Мы же порядочные граждане, хотим быть уверены, что мы не шлем soap-серверу что-то, чего он не ожидает. В конкретно данном случае дополнительный реверанс пришлось сделать для получения типа создаваемого свойства, т. к. исходная wsdl вообще не содержит явных описаний типов значений, а только описания свойств с вложенными описаниями типов.
А вот чтение сообщения мы уже выполним «честной» ФабрикойXDTO для получения объекта XDTO и возможности работы в объектной модели.
Ставим в конец процедуры точку останова, выполняем обработку… Вуаля:
Цель достигнута!
Кластеризация
Окей, сервис готов, можно в прод? :)
Если не страшно, то можно сразу и в прод, однако, я бы на вашем месте помимо обработки ошибок и общего причесывания кода добавил бы еще одну вещь. Node.js штука хоть и быстрая, но не всемогущая. Возможно вам знакома фраза, что «нода – асинхронная, но однопоточная». В новых версиях node.js уже появилась честная поддержка многопоточности, но для простоты воспользуемся другим старым и проверенным механизмом – кластеризацией. А асинхронность обработки в нашем случае есть, но нам не помешает воспользоваться дешевым ускорителем.
Ставим пакет cluster-service с помощью команды:
npm install -g cluster-service
Запускаем наше приложение в командной строке, но в вместо node укажем приложение cservice:
Наш промежуточный сервис запустился в режиме кластера с количеством потоков, равным количеству логических процессоров. Можете выполнить нагрузочное тестирование через тот же SoapUI и замерить количество обрабатываемых запросов в секунду при обычном запуске и при кластеризованном запуске – заметите ощутимую разницу.
Что там было про WS-Addressing?
Ах-да, заголовки, те самые soap-headers, которые не поддерживает 1С. Добавить их довольно просто – для этого в soapClient есть метод addSoapHeaders, в который можно передать либо готовую строку с заголовками, либо JS-объект. Попробуем реализовать добавление пары заголовков семейства WS-Addressing, а именно Action и MessageID.
Для генерации UUID сообщения установим библиотеку uuid:
npm install --save uuid
Добавим ее в секцию импорта библиотек:
const uuidv4 = require("uuid/v4");
Между проверкой указателя на soap-метод и самим вызовом soap-метода добавим заполнение заголовков:
// Создадим объект для хранения заголовков
const wsaHeader = {
MessageID: {
// В качестве значения для заголовка MessageID сгенерируем случайный UUID
$value: uuidv4()
},
Action: {
// Для Action передадим имя вызываемого метода, как того требует протокол
$value: req.body.method
}
};
// Очистим заголовки soap-запроса
soapClient.clearSoapHeaders();
// Добавим новый заголовок
soapClient.addSoapHeader(
wsaHeader, // объект, в котором хранятся заголовки
"WSA", // имя группы заголовков
"wsa", // префикс пространства имен
"http://www.w3.org/2005/08/addressing" // само пространство имен
);
Убедиться в корректности отправляемых заголовков можно через тот же SoapUI или настроив логирование запросов в промежуточном сервере.
Дополнительные вопросы
- А зачем JSON? Можно гонять туда-сюда XML?
- Можно, но зачем, если есть возможность гонять более легковесный JSON, а 1С уже умеет нативно с ним работать?
- Можно ли накрыть авторизацией?
- Можно, причем и веб-приложение (для этого надо добавить еще один middle-ware с авторизацией в вызов app.post), и soap-сервер, который можно поднять в этом же приложении как сервис обратного вызова в случае асинхронного soap-обмена.
Заключение
Вот таким нехитрым способом мы смогли обойти ограничение возможностей платформы 1С, таких как отсутствие поддержки soap-headers и не полной поддержки WSDL-описания.
Не бойтесь использовать другие языки в своей работе. Даже начальный уровень знаний какого-либо языка, фреймворка или технологии может существенно сократить вам время на разработку требуемой функциональности.
Полный код получившегося приложения, а также исходники обработки доступны в репозитории на GitHub.
P.S. В процессе написания статьи я в очередной раз вспомнил, почему я так люблю TypeScript - за ошибки во время компиляции, типизацию и более умную контекстную подсказку. Если у вас еще остались силы, то в качестве домашнего задания можете повторить этот же пример на TypeScript, благо настроить единственный json-файл с конфигом можно тоже почти автоматически.
Специальные предложения
См. также
Программы для исполнения 488-ФЗ: Маркировка товаров Промо
1 января 2019 года вступил в силу ФЗ от 25.12.2018 № 488-ФЗ о единой информационной системе маркировки товаров с использованием контрольных (идентификационных) знаков, который позволяет проследить движение товара от производителя до конечного потребителя. Инфостарт предлагает подборку программ, связанных с применением 488-ФЗ и маркировкой товаров.
Перенос данных КА 1.1 / УПП 1.3 => БП 3.0 (перенос остатков, документов и справочников из "1С:Комплексная автоматизация 1.1" / УПП 1.3 в "1С:Бухгалтерия 3.0"). Обновлен до версий КА 1.1.115.х, УПП 1.3.127.х! Промо
Разработка позволяет перенести остатки по всем счетам бух.учета в программу "1С:Бухгалтерия предприятия 8", ред. 3.0 на выбранную дату начала ведения учета. Также переносятся документы за период и вся необходимая справочная информация. Правила оперативно обновляю при выходе новых релизов. Рассылка обновлений правил бесплатно в течение 12 месяцев. Есть видеодемонстрация проведения переноса данных. Конфигурации при использовании обмена остаются полностью типовыми. Перенос данных возможен в Бухгалтерию 3.0 версии ПРОФ, КОРП или базовую.
24700 руб.
Программы для исполнения 54-ФЗ Промо
С 01.02.2017 контрольно-кассовая техника должна отправлять электронные версии чеков оператору фискальных данных - правила установлены в 54-ФЗ ст.2 п.2. Инфостарт предлагает подборку программ, связанных с применением 54-ФЗ, ККТ и электронных чеков.
Функции СКД: ВычислитьВыражение, ВычислитьВыражениеСГруппировкойМассив 262
08.08.2019 18548 ids79 31
Вакансия Автор новостных обзоров на тему 1С и бухучета, По совместительству Промо
Редакция Infostart.ru будет рада сотрудничеству с 1С-специалистом, умеющим и любящим излагать свои мысли в письменной форме. Если вы работали в IT-изданиях или имеете опыт ведения технологического блога/канала/группы, если сможете сделать обзор обработок из каталога infostart.ru/public/all/, то у вас большое преимущество.
Онлайн-интенсив "Бизнес-процессы для подготовки к экзамену 1С:Специалист по платформе" 12 декабря 2019 г. Промо
На интенсиве будут рассмотрены все теоретические вопросы, связанные с устройством механизма бизнес-процессов – это необходимо для успешной сдачи экзамена 1С:Специалист по платформе. Также, в качестве практического примера, будет решена задача, аналогичная экзаменационной.
777 рублей
СКД - наборы данных и связи между ними, создание собственной иерархии, вложенные отчеты 141
26.07.2019 16717 ids79 8
1С:Предприятие через Интернет. 1С:Fresh Промо
Ведение бухгалтерского и налогового учет, сдача отчетности, управление бизнесом из любой точки мира. Привычные программы «1С» через Интернет без приобретения коробочных программ.
Перенос данных УПП 1.3 => ERP 2 (ЕРП) / УТ 11 / КА 2.х (обработка переноса документов, остатков и справочников из "1С:Управление производственным предприятием, ред. 1.3" в ERP / УТ 11 / КА 2). Обновлен до УПП 1.3.127.х, КА 2.4.10.х и ERP 2.4.10.х! Промо
Обработка позволяет переносить из УПП 1.3 в ERP 2 документы за выбранный период и остатки. Типовая обработка от фирмы 1С документы не переносит. Также исправлены ошибки типовой обработки. При выходе новых релизов обновление высылается бесплатно в течение года. Разработка будет полезна фирмам-франчайзи, которые периодически выполняют такой перенос данных для заказчиков. Вы можете один раз приобрести обработку переноса, и потом бесплатно получать обновления при выходе новых релизов конфигураций 1С.
29700 руб.
Многопоточное ускорение однопользовательских нагрузок в 1С + Microsoft SQL Server 2017 179
11.06.2019 13694 dmurk 134
1СПАРК РИСКИ. Сервис оценки благонадежности контрагентов. Промо
СПАРК помогает предотвратить мошенничество со стороны компаний и предпринимателей, благодаря актуальным сведениям о компаниях и системе выявления факторов риска.Сервис позволяет управлять налоговыми рисками и комплексно оценивать благонадежность контрагентов.
Перенос данных БП 3.0 => УТ 11 / КА 2 / ERP 2 (ЕРП) (перенос остатков, документов и справочной информации из "1С:Бухгалтерия предприятия 8", ред.3.0). Обновлено до БП 3.0.73.х, УТ 11.4.10.х, КА 2.4.10.х., ERP 2.4.10.х! Промо
Переносятся документы за выбранный период, справочная информация и остатки по счетам бух. учета в программу УТ 11 / КА 2 / ЕРП 2 (ERP). Переносятся все возможные виды операций ввода остатков на нужную дату. Есть отбор по периоду переноса документов и фильтр по организации, доступен выбор даты ввода остатков. Если нужно переносить что-то дополнительно, то обычно бесплатно добавляем это в перенос . Смотрите видеодемонстрацию со звуком - советами по переносу и рекомендациями настройки программ.
29700 руб.
Готовые переносы данных из различных конфигураций 1C Промо
Рекомендуем готовые решения для переноса данных из различных конфигураций 1C. C техподдержкой от разработчиков и гарантией от Инфостарт.
Git-репозитории для 1С-кода (опыт использования при небольших проектах) 202
28.03.2019 15546 ellavs 83
Подборка программ для взаимодействия с ЕГАИС Промо
ЕГАИС (Единая государственная автоматизированная информационная система) - автоматизированная система, предназначенная для государственного контроля за объёмом производства и оборота этилового спирта, алкогольной и спиртосодержащей продукции. Инфостарт рекомендует подборку проверенных решений для взаимодействия с системой.
Разработка и сценарное тестирование с Vanessa-ADD. Отчетность Allure. Автоматизация запуска сценариев 121
26.02.2019 11852 Vladimir Litvinenko 25
Новый раздел на Инфостарте - Electronic Software Distribution Промо
Инфостарт напоминает: на нашем сайте можно купить не только ПО, связанное с 1С. В нашем арсенале – ESD-лицензии на ПО от ведущих вендоров: Microsoft, Kaspersky, ESET, Dr.Web, Аскон и другие.
- Низкие цены, без скрытых платежей и наценок
- Оперативная отгрузка
- Возможность оплаты с личного счета (кешбек, обмен стартмани на рубли и т.п.)
- Покупки идут в накопления для получения скидочных карт лояльности Silver (5%) и Gold (10%)
Разработка и сценарное тестирование с Vanessa-ADD. Собственные шаги и библиотеки. Экспортные сценарии 114
07.02.2019 10588 Vladimir Litvinenko 13
Возможности типовых шаблонов ограничения доступа на уровне записей (RLS) 173
03.02.2019 19459 ids79 9