[Программирование, C++, Распределённые системы, Микросервисы] С чего начать писать микросервис на C++
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В данной статье я буду опираться на использование libevent в рамках debian+gcc+cmake, но на других unix-подобных ОС сложностей возникнуть не должно(для windows потребуется сборка из исходников и доработка FindLibEvent.cmake файла)
ПредисловиеОколо 3х лет занимаюсь разработкой микросервисов, однако изначального понимания подходящего стека технологий у меня не было. Испробовал множество различных подходов (одними из которых были OpenDDS и apache-thrift), но в конце концов остановился на RestApi. RestApi общается по средствам HTTP-запросов, которые в свою очередь представляют структуру данных из заголовков и тела запроса передаваемые через сокет. Первым на что я обратил внимание это boost/asio который предоставляет tcp-сокеты, но тут возникают сложности с объемами разработки:
- Надо написать корректный прием данных по сокету
- Самописный парсинг заголовков
- Самописный парсинг GET-параметров
- Маршрутизацию путей обращения
Второй на очереди была POCO (POcket COmponents) в которой есть более высокоуровневый HTTP сервер, но по прежнему в нем оставалась проблема с кучей самописного функционала. К тому же данное средство чуть более тяжеловесное и предоставляет функционал который может не потребоваться (немного перегружает наши микросервисы). POCO заточена под другие задачи нежели микросервисы.Поэтому далее поговорим про libevent на котором я и остановился.Почему libevent?
- Легковесный
- Быстрый
- Стабильный
- Кроссплатформенный
- Из коробки предустановлен в большинстве unix-подобных ОС
- Используется множеством разработчиков (легче найти сотрудников кто знаком с данной технологией)
- Есть встроенный маршрутизатор (router)
Однако у libevent есть и очень существенный минус. Данная библиотека в своем публичном интерфейсе использует си-стайл код. Это означает что она заточена на использование "сырых" указателей в купе со встроенными средствами очистки памяти, что в современном C++ категорически недопустимо из-за возможных проблем с утечками памяти (рекомендуется использовать умные указатели).
В примерах данной статьи будут использованы возможные способы защиты от утечек памяти, но тем не менее советую пробегаться по коду средствами для профилирования (например Valgrind).
ЛинковкаБиблиотека libevent предоставляется в составе пакета libevent-dev и стоит из коробки на множестве unix-подобных ОС. Чтобы узнать установлен ли у вас данный пакет, можно ввести команду dpkg -l | grep event или аналогичную для вашей системы.Для начала нам нужно найти библиотеку в системе, для этого необходимо написать FindLibEvent.cmake (я создаю такие файлы в директории корень_проекта/cmake_modules)
# Находим путь до папки с заголовочными файлами и записываем в ${LIBEVENT_INCLUDE_DIR}
find_path(LIBEVENT_INCLUDE_DIR event.h
PATHS
/usr/local
/opt
PATH_SUFFIXES
include
)
# Находим бинарные файлы библиотеки и записываем в ${LIBEVENT_LIB}
find_library(LIBEVENT_LIB
NAMES
event
PATHS
/usr/local
/opt
PATH_SUFFIXES
lib
lib64
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
LIBEVENT_LIB
LIBEVENT_INCLUDE_DIR
)
Так же для удобства можно создать файл импорта библиотеки (я создаю такие файлы в директории корень_проекта/imported/libevent.cmake)
find_package(LibEvent REQUIRED) # Запускаем наш FindLibEvent.cmake
add_library(libevent STATIC IMPORTED GLOBAL) # Тут мы создаем target библиотеки с которым и будем работать
# Указываем нашему target-у пути до папки с заголовочными файлами найденными в FindLibEvent.cmake
set_target_properties(libevent PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBEVENT_INCLUDE_DIR})
# Указываем нашему target-у пути до бинарников найденных в FindLibEvent.cmake
set_target_properties(libevent PROPERTIES IMPORTED_LOCATION ${LIBEVENT_LIB})
Теперь libevent библиотека находится cmake-ом и можно приступить к линковке.Если мы решили использовать импорт, то нам достаточно вызвать всего 1 команду
target_link_libraries(${PROJECT_NAME}
PUBLIC
libevent
)
Если мы не хотим создавать лишние файлы импорта, линкуем то что нашел наш FindLibEvent.cmake
find_package(LibEvent REQUIRED)
target_link_libraries(${PROJECT_NAME}
PUBLIC
${LIBEVENT_LIB}
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${LIBEVENT_INCLUDE_DIR}
)
Чтобы запустить HTTP сервер, нужно
// Подключаем заголовочный файл, содержащий:
// * Базовый слушатель запросов
// * Буферы для работы с передаваемыми данными
// * Средства для работы с HTTP(парсер, маршрутизатор и пр.)
#include <evhttp.h>
// Создаем слушатель через умный указатель
auto listener = std::make_shared<event_base, decltype(&event_base_free)>(event_base_new(), &event_base_free);
// Создаем HTTP сервер через умный указатель
auto server = std::make_shared<evhttp, decltype(&evhttp_free)> (evhttp_new(listener.get()), &evhttp_free);
// Настраиваем маршрутизатор
// Устанавливаем обработчик на пути для которых нету собственных обработчиков
evhttp_set_gencb(server.get(), [](evhttp_request*, void*) {}, nullptr);
// Устанавливаем обработчик на определенный путь обращения
evhttp_set_cb (server.get(), "/my_path", [](evhttp_request*, void*) {}, nullptr);
// Запускаем прослушку сервера
return event_base_dispatch(listener.get());
Теперь наш сервер умеет принимать запросы, но любой сервер должен отвечать клиентскому приложению. Для этого в обработчиках генерируем response-ы
// Создаем буфер для тела ответа
auto buffer = std::make_shared<evbuffer, decltype(&evbuffer_free)>(evbuffer_new(), &evbuffer_free);
evbuffer_add(buffer, msg.c_str(), msg.length()); // Записываем тело в буфер
evhttp_send_reply(request, HTTP_OK, "", buffer); // Отправляем ответ
Мы доделали полноценное общение у нашего сервера, теперь поговорим о получении полезной информации из клиентских запросов.Первым делом нужно разобрать GET-параметры. Это те параметры, которые передаются в URI запроса(например http://www.hostname.ru?key=value)
struct evkeyvalq params;
evhttp_parse_query(request->uri, ¶ms); // Разбираем GET параметры
// Таким способом можно получить значение GET-параметра по его ключу
std::string value = evhttp_find_header(¶ms, "key");
// Таким способом можно перебрать все GET-параметры
for (auto it = params.tqh_first; it != nullptr; it = it->next.tqe_next)
std::cout << it->key << ":" << it->value << std::endl;
// Далее необходимо почистить за собой
evhttp_clear_headers(¶ms);
Далее нужно получить тело запроса
auto input = request->input_buffer; // Получаем буфер для чтения тела запроса
// Так лучше память не выделять, но более безопасных способов я не нашел
auto length = evbuffer_get_length(input);
char* data = new char[length];
evbuffer_copyout(input, data, length); // Читаем тело запроса
std::string body(data, length); // Упаковываем в более безопасную сущность
delete[] data; // Чистим за собой
return body;
Внимание Callback-функции не поддерживают прерываний (захвата значений лямбда-функциями) поэтому внутри callback можно использовать только статические члены и методы!
===========
Источник:
habr.com
===========
Похожие новости:
- [Системное администрирование, Программирование, IT-инфраструктура, Apache] 5 вещей, о которых должен знать любой разработчик Apache Kafka (перевод)
- [Программирование, Терминология IT, Управление разработкой] Энтерпрайз разработка с нуля
- [Системное администрирование, Программирование, Git, Разработка под Linux, DevOps] DevOps: автоматизация инфраструктуры на примере Terraform, docker, bash, prometheus exporters, Gitlab и WireGuard
- [Программирование, Совершенный код, C++, Лайфхаки для гиков] Прочти меня: код, который не выбесит соседа
- [Программирование, Java, Kotlin, Интервью] Большой разговор с новым Kotlin Project Lead Романом Елизаровым
- [C++] Доступ к элементам std::tuple во время исполнения программы
- [Программирование, C++, Промышленное программирование, Программирование микроконтроллеров] Маленькие хитрости для STM32
- [Программирование, Java, Разработка мобильных приложений, Разработка под Android] Как написать простое Android ToDo-приложение на Java
- [JavaScript, Программирование, VueJS] Сделаем худший Vue.js в мире (перевод)
- [Информационная безопасность, Программирование, Разработка под Android] Уязвимости Android 2020
Теги для поиска: #_programmirovanie (Программирование), #_c++, #_raspredelennye_sistemy (Распределённые системы), #_mikroservisy (Микросервисы), #_libevent, #_microservices, #_http, #_programmirovanie (
Программирование
), #_c++, #_raspredelennye_sistemy (
Распределённые системы
), #_mikroservisy (
Микросервисы
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:14
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В данной статье я буду опираться на использование libevent в рамках debian+gcc+cmake, но на других unix-подобных ОС сложностей возникнуть не должно(для windows потребуется сборка из исходников и доработка FindLibEvent.cmake файла)
В примерах данной статьи будут использованы возможные способы защиты от утечек памяти, но тем не менее советую пробегаться по коду средствами для профилирования (например Valgrind).
# Находим путь до папки с заголовочными файлами и записываем в ${LIBEVENT_INCLUDE_DIR}
find_path(LIBEVENT_INCLUDE_DIR event.h PATHS /usr/local /opt PATH_SUFFIXES include ) # Находим бинарные файлы библиотеки и записываем в ${LIBEVENT_LIB} find_library(LIBEVENT_LIB NAMES event PATHS /usr/local /opt PATH_SUFFIXES lib lib64 ) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( LIBEVENT_LIB LIBEVENT_INCLUDE_DIR ) find_package(LibEvent REQUIRED) # Запускаем наш FindLibEvent.cmake
add_library(libevent STATIC IMPORTED GLOBAL) # Тут мы создаем target библиотеки с которым и будем работать # Указываем нашему target-у пути до папки с заголовочными файлами найденными в FindLibEvent.cmake set_target_properties(libevent PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBEVENT_INCLUDE_DIR}) # Указываем нашему target-у пути до бинарников найденных в FindLibEvent.cmake set_target_properties(libevent PROPERTIES IMPORTED_LOCATION ${LIBEVENT_LIB}) target_link_libraries(${PROJECT_NAME}
PUBLIC libevent ) find_package(LibEvent REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC ${LIBEVENT_LIB} ) target_include_directories(${PROJECT_NAME} PUBLIC ${LIBEVENT_INCLUDE_DIR} ) // Подключаем заголовочный файл, содержащий:
// * Базовый слушатель запросов // * Буферы для работы с передаваемыми данными // * Средства для работы с HTTP(парсер, маршрутизатор и пр.) #include <evhttp.h> // Создаем слушатель через умный указатель auto listener = std::make_shared<event_base, decltype(&event_base_free)>(event_base_new(), &event_base_free); // Создаем HTTP сервер через умный указатель auto server = std::make_shared<evhttp, decltype(&evhttp_free)> (evhttp_new(listener.get()), &evhttp_free); // Настраиваем маршрутизатор // Устанавливаем обработчик на пути для которых нету собственных обработчиков evhttp_set_gencb(server.get(), [](evhttp_request*, void*) {}, nullptr); // Устанавливаем обработчик на определенный путь обращения evhttp_set_cb (server.get(), "/my_path", [](evhttp_request*, void*) {}, nullptr); // Запускаем прослушку сервера return event_base_dispatch(listener.get()); // Создаем буфер для тела ответа
auto buffer = std::make_shared<evbuffer, decltype(&evbuffer_free)>(evbuffer_new(), &evbuffer_free); evbuffer_add(buffer, msg.c_str(), msg.length()); // Записываем тело в буфер evhttp_send_reply(request, HTTP_OK, "", buffer); // Отправляем ответ struct evkeyvalq params;
evhttp_parse_query(request->uri, ¶ms); // Разбираем GET параметры // Таким способом можно получить значение GET-параметра по его ключу std::string value = evhttp_find_header(¶ms, "key"); // Таким способом можно перебрать все GET-параметры for (auto it = params.tqh_first; it != nullptr; it = it->next.tqe_next) std::cout << it->key << ":" << it->value << std::endl; // Далее необходимо почистить за собой evhttp_clear_headers(¶ms); auto input = request->input_buffer; // Получаем буфер для чтения тела запроса
// Так лучше память не выделять, но более безопасных способов я не нашел auto length = evbuffer_get_length(input); char* data = new char[length]; evbuffer_copyout(input, data, length); // Читаем тело запроса std::string body(data, length); // Упаковываем в более безопасную сущность delete[] data; // Чистим за собой return body; Внимание Callback-функции не поддерживают прерываний (захвата значений лямбда-функциями) поэтому внутри callback можно использовать только статические члены и методы!
=========== Источник: habr.com =========== Похожие новости:
Программирование ), #_c++, #_raspredelennye_sistemy ( Распределённые системы ), #_mikroservisy ( Микросервисы ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:14
Часовой пояс: UTC + 5