Внешняя компонента как REST-API-компонента...

01.11.21

Разработка - Разработка внешних компонент

...и совсем немного кода на С[++]...

Всем привет!

 

ВВЕДЕНИЕ

Я в последнее время делал несколько проектов на Python, суть которых сводилась к внешнему сервису, работающему как очередь оборудования и механизм контроля. Но Python - это хоть и очень просто, но не так быстро, поэтому время от времени я пытался найти, как сделать какой-нить сервис на С[++] (плюсы в скобках как бы намекают, что от плюсов там по большому счету только int перед main). В итоге нашел, что позволило мне все свои механизмы, написанные на Python, достаточно легко и непринужденно переработать в механизмы, работающие на С++.

 

БИБЛИОТЕКА RESTBED

Периодически набирая в гуглах что-то типа "HTTP-сервер на С++", я натыкался на разные решения с костылями и палками, но в последний раз наткнулся на отличную (на мой скромный взгляд) библиотеку, которая достаточно просто позволяет организовать HTTP-сервис, и при этом (и это было важно) существует и отлично работает даже на DEBIAN для Rispberry PI (ниже приведу сравнительный тест).

Сама по себе библиотека очень проста и в сути своей оперирует всего несколькими сущностями: настройкой соединения и ссылкой на сервисы. У библиотеки достаточно большой функционал (включая авторизацию, SSL/HTTPS, многопоточность и все то, что еще может нам потребоваться).

 

ПРИМЕР ПРОСТОГО СЕРВИСА

Давайте замутим простой сервис, добавляющий в некий массив значения и возвращающий нам, есть ли такое значение в этом массиве.

Для начала установим соответствующую библиотеку (для Linux, как это сделать в винде - я без понятия, если, конечно, что не WSL[2]).

sudo apt install librestbed-dev librestbed0

Тут у нас два пакета - сама библиотека и ее заголовочные файлы для разработчиков.

Дальше при сборке нам достаточно будет указать опцию "-lrestbed" и все.

Напишем простой код:

 

ЗАГОЛОВКИ

#include <stdlib.h>
#include <map>
#include <string>
#include <memory>
#include <cstdlib>
#include <restbed>

1. Стандартная библиотека - нужна нам для преобразования параметра командной строки в число функцией atoi (для указания номера порта, на котором весить сервис).

2. Map - "ассоциативный массив", который всегда упорядочен по ключу. Скорость доступа к элементуO( Log2N ).

3. Строки - куда без них...

4. Умные указатели - в нашем случае это shared_ptr, который... Сами где-нить прочитайте, а то это может быть надолго. В нашем случае понимать всю суть этого не нужно.

5. Честно говоря, сам с этим не разбирался. Но нам пока это тоже не нужно.

6. Ну и сама наша библиотека для организации HTTP-сервиса.

И еще немного кода...

using namespace std;
using namespace restbed;

map <string, string> srvArray;

Собственно, это у нас определение пространств имен (чтобы не писать перед каждой функцией и типом std::) и нашего "ассоциативного массива".

 

"ХЭНДЛЕРЫ"

void get_set_method_handler( const shared_ptr< Session > ss )
{
    const auto req = ss->get_request();
    auto data = req->get_path_parameter( "data" );
    srvArray[ data ] = req->get_path_parameter( "value" );
    ss->close( OK, "OK", { { "Content-Length", "2" } } );
}

void get_get_method_handler( const shared_ptr< Session > ss )
{
    const auto req = ss->get_request();
    auto data = req->get_path_parameter( "data" );
    if (!srvArray[ data ].empty()) {
        const string ret = srvArray[ data ];
        //cout << ret << endl;
        ss->close( OK, ret, { { "Content-Length", ::to_string( ret.size() ) } } );
    } else {
        //cout << "EMPTY" << endl;
        ss->close( OK, "EMPTY", { { "Content-Length", "5" } } );
    }
}

Здесь у нас два обработчика событий HTTP-сервиса, которые мы чуть ниже зарегистрируем.

Первый обработчик устанавливает ключ и значение по приехавшим параметрам. Второй обработчик возвращает установленный ранее параметр или строку "EMPTY", если такое значение мы еще не устанавливали.

Алгоритм тут достаточно прост:

1. Получаем из сессии (аргумент функции) текущий запрос (в принципе, все как в 1С).

2. Получаем из запроса именованный параметр(ы) (задается в шаблоне, увидите ниже).

3. Устанавливаем в ключ значение или извлекаем значение по ключу.

4. Возвращаем "ОК, значение или "ENPTY" (если значение с таким ключом еще не было установлено).

Ну и давайте перейдем к самому интересному...

 

ФУНКЦИЯ MAIN

int main ( const int carg, const char** arg)
{
    int port = 8080;
    if ( carg > 1 ) port = atoi( arg[1] );

    auto resource_set = make_shared< Resource >();
    resource_set->set_path( "/set/{data: .*}/{value: .*}" );
    resource_set->set_method_handler( "GET", get_set_method_handler );

    auto resource_get = make_shared< Resource >();
    resource_get->set_path( "/get/{data: .*}" );
    resource_get->set_method_handler( "GET", get_get_method_handler );

    auto settings = make_shared< Settings >();
    settings->set_port( port );
    settings->set_default_header( "Connection", "close" );

    Service service;
    service.publish( resource_set );
    service.publish( resource_get );

    service.start( settings );
}

Вот и вся программа на "супер-пупер-сложном" языке.

1. Указываем порт по умолчанию "8080".

2. Проверяем, есть ли параметры в командной строке.

3. Если параметры есть, то устанавливаем порт из первого (в действительности - второго).

4. Определяем первый наш HTTP-ресурс (SET - установка значения), как умный указатель с соответствующим типом значения.

5. Устанавливаем шаблон "/set/{data: .*}/{value: .*}". Я несколько минут потратил, пока до меня дошло, а Вы?

6. Определяем второй ресурс (GET - получение установленного ранее значения или "EMPTY").

7. Создаем настройку, передаем в нее порт, устанавливаем заголовки по умолчанию.

8. Создаем сервис и регистрируем там наши ресурсы.

9. Стартуем сервис с нашими настройками.

Ну и осталось извлечь пользу.

 

ДЕРНЕМ СЕРВИС ИЗ 1С

Ну тут тоже все просто, но чуть усложним и проведем нагрузочный тест.

 
 Код на 1C

 

Процедура ОтправитьВСервисНаСервере()
	СреднееВремя = 0;
	МаксВремя = 0;
	МинВремя = 100000000;
	ВсегоВремя = 0;

	Запрос = Новый Запрос(
		"ВЫБРАТЬ
		|	Номенклатура.Наименование КАК Наименование,
		|	Номенклатура.Код КАК Код
		|ИЗ
		|	Справочник.Номенклатура КАК Номенклатура");
	
	С = Новый HTTPСоединение("172.23.38.82",8080,,,,10);

	Т = Запрос.Выполнить().Выгрузить();
	Для Каждого Ст ИЗ Т Цикл 
		З = Новый HTTPЗапрос(СтрШаблон("/set/%1/%2",ст.код, КодироватьСтроку(СтрЗаменить(ст.Наименование,"/"," "),СпособКодированияСтроки.URLВКодировкеURL)));
		Время = ТекущаяУниверсальнаяДатаВМиллисекундах();
		О = С.Получить(З);
		ВремяТ = ТекущаяУниверсальнаяДатаВМиллисекундах() - Время;
		
		ВсегоВремя = ВсегоВремя + ВремяТ;
		МинВремя = Мин( МинВремя, ВремяТ);
		МаксВремя = Макс( МаксВремя, ВремяТ);
		
		Если НЕ О.КодСостояния = 200 Тогда 
			Сообщить( "Ошибка! " + О.ПолучитьТелоКакСтроку() );
		КонецЕсли;
	КонецЦикла;
	
	Сообщить(
		СтрШаблон( "Статистика SET:
		|Всего запросов: %4,
		|Минимальное время запроса: %1 мс,
		|Максимальное время запроса: %2 мс,
		|Среднее время запроса: %3 мс.", МинВремя, МаксВремя, Цел(ВсегоВремя / Т.Количество()), Т.Количество() )
		);
	
	
	Для Каждого Ст ИЗ Т Цикл 
		З = Новый HTTPЗапрос(СтрШаблон("/get/%1",ст.код));

		Время = ТекущаяУниверсальнаяДатаВМиллисекундах();
		О = С.Получить(З);
		ВремяТ = ТекущаяУниверсальнаяДатаВМиллисекундах() - Время;
		
		ВсегоВремя = ВсегоВремя + ВремяТ;
		МинВремя = Мин( МинВремя, ВремяТ);
		МаксВремя = Макс( МаксВремя, ВремяТ);

		//Сообщить( Ст.Код + "/" + О.ПолучитьТелоКакСтроку() );
	КонецЦикла;
	
	Сообщить(
		СтрШаблон( "Статистика GET:
		|Всего запросов: %4,
		|Минимальное время запроса: %1 мс,
		|Максимальное время запроса: %2 мс,
		|Среднее время запроса: %3 мс.", МинВремя, МаксВремя, Цел(ВсегоВремя / Т.Количество()), Т.Количество() )
		);
	
КонецПроцедуры

Ну суть кода проста - создаем соединение, дергаем SET с кодом товара в качестве ключа и закодированном наименовании номенклатуры в качестве значения. Дальше дергаем GET с кодом товара. В результат выводим ошибки и измеренное время доступа к сервису.

 
 Результат на большом и красивом компьютере

Ошибка! Argument is not a valid URI: http://localhost/set/000000047/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%903%20500%D0%BB%20%22Ballet%20Premier%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20161%%20CIE,%20%D0%90%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%20(%D1%80091745,%20%D0%BA398664)


Ошибка! Argument is not a valid URI: http://localhost/set/000000048/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%903%20500%D0%BB%20%22%D0%A1%D0%B2%D0%B5%D1%82%D0%BE%D0%BA%D0%BE%D0%BF%D0%B8%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20146%%20CIE,%20%D0%A1%20%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20CIE%20(%D0%BA28993,%20%D1%80007221)


Ошибка! Argument is not a valid URI: http://localhost/set/000000049/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%904%20500%D0%BB%20%22Ballet%20Brilliant%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20168%%20CIE,%20%D0%90+%20%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20(%D1%80212721)


Ошибка! Argument is not a valid URI: http://localhost/set/000000050/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%904%20500%D0%BB%20%22IQ%20AllRound%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20162%%20CIE(%D0%BA306383,%20%D1%80075193)


Ошибка! Argument is not a valid URI: http://localhost/set/000000051/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%904%20500%D0%BB%20%22%D0%A1%D0%B2%D0%B5%D1%82%D0%BE%D0%BA%D0%BE%D0%BF%D0%B8%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20146%%20CIE,%20%D0%A1%20%D0%9A%D0%BB%D0%B0%D1%81%D1%81%20(%D1%80000877,%20%D0%BA398657)


Ошибка! Argument is not a valid URI: http://localhost/set/000000052/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%904%20500%D0%BB%20%22%D0%A1%D0%BD%D0%B5%D0%B3%D1%83%D1%80%D0%BE%D1%87%D0%BA%D0%B0%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20146%%20CIE%20(%D0%BA306647)


Ошибка! Argument is not a valid URI: http://localhost/set/000000053/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%904%20500%D0%BB%20Ballet%20%22Classic%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20153%%20CIE,%20%D0%92%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%20(398639,%20398661)


Ошибка! Argument is not a valid URI: http://localhost/set/000000054/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%904%20500%D0%BB%20Ballet%20%22Premier%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20161%%20CIE,%20%D0%90%20%D0%BA%D0%BB%D0%B0%D1%81%D1%81%20(%D1%80066047,%20%D0%BA398663)


Ошибка! Argument is not a valid URI: http://localhost/set/000000055/%D0%91%D1%83%D0%BC%D0%B0%D0%B3%D0%B0%20%D0%BE%D1%84%D0%B8%D1%81%D0%BD%D0%B0%D1%8F%20%D1%84-%D1%82%20%D0%905%20500%D0%BB%20%22OfficeSpace%22%20%D0%B1%D0%B5%D0%BB%D0%B8%D0%B7%D0%BD%D0%B0%20149%%20CIE%201%2010%20(264198)


Ошибка! Argument is not a valid URI: http://localhost/set/000000403/%D0%9F%D0%BE%D0%B4%D1%81%D1%82%D0%B0%D0%B2%D0%BA%D0%B0%20%D0%90%D0%BA%D1%86%D0%B8%D1%8F%2030%%20205*175%20%D0%BC%D0%BC.%20(%D0%BA416133)


Статистика SET:
Всего запросов: 2 000,
Минимальное время запроса: 0 мс,
Максимальное время запроса: 34 мс,
Среднее время запроса: 9 мс.


Статистика GET:
Всего запросов: 2 000,
Минимальное время запроса: 0 мс,
Максимальное время запроса: 44 мс,
Среднее время запроса: 19 мс.

Сначала у меня для ряда позиций система возвратила ошибку, т.к. не смогла определить в запросе соответствие шаблону. Таких строк в моем справочнике не так и много - всего 10 из 2000. Над причиной мне было лень думать, но, например, символ "/" уже приведет к подобной ошибке.

Ну и скорость обращений с виртуалки на винде к хост-системе на убунту достаточно высокая - в среднем запрос выполняется за 9 мс для записи и 19 мс для чтения (не знаю, почему так). Максимальное время не превышает 50 мс. Давайте протестируем это на Rispberry PI 3 B.

sergey@sergey-X570-AORUS-ELITE:~$ ssh pi@172.23.38.105
pi@raspberrypi:~ $ g++ exmpl.cpp -lrestbed
pi@raspberrypi:~ $ ./a.out

Зашли на R PI, скомпилировали программку, запустили...

 
 Результат на меленьком, но гордом Rispberry PI 3 B

 Статистика SET:
Всего запросов: 2 000,
Минимальное время запроса: 0 мс,
Максимальное время запроса: 94 мс,
Среднее время запроса: 15 мс.


Статистика GET:
Всего запросов: 2 000,
Минимальное время запроса: 0 мс,
Максимальное время запроса: 125 мс,
Среднее время запроса: 31 мс.

С учетом того, что до Rispberry надо ехать через WIFI-роутер, то результат вполне себе приличный.

Ну все, всем спасибо за внимание. Надеюсь, куму-то данная статья поможет в решении каких-то своих интересных задач.

*** тестовая база 1С была развернута на бесплатной версии 1С для обучения программированию на виртаульной машине на базе VirtualBox 6.1.26, на которой была установлена винда 10 с опцией "у меня нет ключа". Подробнее тут: ТЫЛЫЩЬ!

REST. HTTP REST-API сервисы C++

См. также

Сайты и интернет-магазины WEB-интеграция Системный администратор Программист Пользователь Платформа 1С v8.3 Конфигурации 1cv8 1С:Управление торговлей 11 Автомобили, автосервисы Россия Управленческий учет Платные (руб)

Интеграционный модуль обмена между конфигурацией Альфа Авто 5 и Альфа Авто 6 и порталом AUTOCRM. Данный модуль универсален. Позволяет работать с несколькими обменами AUTOCRM разных брендов в одной информационной базе в ручном и автоматическом режиме.

36000 руб.

03.08.2020    18003    18    22    

17

Сайты и интернет-магазины Интеграция WEB-интеграция Платформа 1С v8.3 Конфигурации 1cv8 Управленческий учет Платные (руб)

Интеграция 1С и Битрикс 24. Разработка имеет двухстороннюю синхронизацию 1С и Bitrix24 задачами. Решение позволяет создавать пользователя в 1С из Битрикс24 и наоборот. Данная разработка технически подходит под все основные конфигурации линейки продуктов 1С:Предприятие 8.3 (платформа начиная с 8.3.23). При приобретении предоставляется 1 месяц бесплатных обновлений разработки. Доступна демо-версия продукта с подключением Вашего Битрикс24

7200 руб.

04.05.2021    20144    13    19    

18

WEB-интеграция 8.3.8 Конфигурации 1cv8 Автомобили, автосервисы Беларусь Украина Россия Казахстан Управленческий учет Платные (руб)

Расширение предназначено для конфигурации "1С:Предприятие 8. Управление Автотранспортом. ПРОФ". Функционал модуля: 1. Заполнение регистров сведений по подсистеме "Мониторинг", а именно: события по мониторингу, координаты по мониторингу, пробег и расход по мониторингу, текущее местоположение ТС по мониторингу 2. Заполнение путевого листа: пробег по мониторингу, время выезда/заезда, табличная часть ГСМ, места стоянок по геозонам. 3. Отчеты по данным загруженным в регистры сведений. 4. Предусмотрена автоматическая загрузка данных в фоновом режиме (условия работы данной загрузке читайте в описании товара) Модуль работает без включенной константы по настройкам мониторинга. Модуль формы предоставляется с открытым кодом, общий модуль защищен. Любой заинтересованный пользователь, имеет возможность скачать демо-версию расширения.

22656 руб.

25.05.2021    14553    42    8    

18

WEB-интеграция Программист Руководитель проекта Платформа 1С v8.3 Конфигурации 1cv8 1С:Франчайзи, автоматизация бизнеса Платные (руб)

Расширение значительно упрощает написание API на 1С. Веб программисты получают простой и понятный доступ к 1С. Описание API создаётся автоматически и представляется в виде удобном как для человека, так и для программной обработки.

24000 руб.

27.09.2024    1713    1    0    

3
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. Nefilimus 75 01.11.21 15:14 Сейчас в теме
Название переменных божественны =)) Я что-то не понял в чём фишка... Это же обычный http запрос, не?
Сто27001; +1 Ответить
2. starik-2005 3092 01.11.21 15:15 Сейчас в теме
(1)
Я что-то не понял в чём фишка.
Это веб-сервис на С++. Больше ничего необычного...
Nefilimus; +1 Ответить
9. triviumfan 97 08.11.21 10:27 Сейчас в теме
(2) А можно узнать для чего? Если можно создать такой же сервис в самой 1с. Не понимаю сути этой ВК.
10. starik-2005 3092 08.11.21 13:26 Сейчас в теме
(9)
Не понимаю сути этой ВК.
Для обработки 10к запросов в секунду.
Nefilimus; +1 Ответить
3. SGordon1 01.11.21 15:22 Сейчас в теме
4. starik-2005 3092 01.11.21 15:25 Сейчас в теме
(3)
А что нужно для https?
Если перейдете по сцылке на гит, то найдете там в документации примеры, в которых есть и HTTPS.
https://github.com/Corvusoft/restbed/blob/master/documentation/example/HTTP­S_SERVICE.md
5. gybson 01.11.21 15:30 Сейчас в теме
Как механизм интеграции сгодится, но выгоды перед питоном не видно вот так сразу
6. starik-2005 3092 01.11.21 15:32 Сейчас в теме
(5)
но выгоды перед питоном не видно вот так сразу
Выгоды перед питоном не видно до тех пор, пока не придется что-то простое, но большое, обрабатывать. С++ на несколько порядков быстрее питона может в ряде случаев работать, при этом требует сильно меньше ресурсов. При том современный С++ - это уже не сильно сложнее питона...
cobroid; davdykin; +2 Ответить
7. gybson 01.11.21 16:50 Сейчас в теме
(6)может то оно может, только программист нужен опытный. Современный С++ достаточно сложен, чтобы утонуть в утечках памяти, исключениях и прочем. Масштабирование сервиса под вопросом. У питона то под капотом такой же С++ код, только с ним работать проще.
8. starik-2005 3092 01.11.21 17:00 Сейчас в теме
(7)
чтобы утонуть в утечках памяти, исключениях и прочем
C STL и умными указателями \то будет сложно сделать. Исключения в 99% случаев от того, что переменной не выделена память, но сейчас это статическое выделение или с помощью объектов STL, так что утечки тоже давно уже канули в лету (если, конечно, не программировть на С варианта 90-х).
11. user1886088 18.12.22 19:05 Сейчас в теме
А чего не GO? Там реализовать http сервер на порядок проще с со скоростью у него тоже все хорошо.
12. starik-2005 3092 30.12.22 14:35 Сейчас в теме
(11)
Там реализовать http сервер на порядок проще с со скоростью у него тоже все хорошо
Приведите пример этого "проще".
13. ivan1703 79 20.04.23 21:20 Сейчас в теме
еще проще )))

#include "httplib.h"

int main()
{
    httplib::Server listener;
    listener.Get("/", [](const Request&, Response& res){
        res.set_content("Hello it's c++ !!!", "application / json; charset = utf-8");
    });
    listener.listen("0.0.0.0", 80);
}
Показать
14. starik-2005 3092 20.04.23 21:40 Сейчас в теме
Оставьте свое сообщение