Внешняя компонента как REST-API-компонента…
…и совсем немного кода на С[++]…
- Описание
- Подробнее
Описание
Всем привет!
ВВЕДЕНИЕ
Я в последнее время делал несколько проектов на 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С
Ну тут тоже все просто, но чуть усложним и проведем нагрузочный тест.