Skip to content
/ bjo Public
generated from bersen66/project-template

set of utilities for writing rest services on cpp20

Notifications You must be signed in to change notification settings

bersen66/bjo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bjo

Данная библиотека призвана упростить процесс написания асинхронных приложений, использующих boost::asio и boost::beast. Использование bjo делает c++ код, решающий типовые высокоуровневые задачи, более читабельным и простым для восприятия, без ущерба производительности.

Библиотека bjo предоставляет удобные и эффективные абстракции для написания асинхронных приложений. Приоритетный способ написания асинхронного кода в bjo - это корутины, которые вошли в стандарт языка C++ в 2020 году. Предполагается, что пользователь библиотеки будет использовать именно их, в процессе написания кода, несмотря на то, что альтернативные подходы к написанию асинхронного кода тоже поддерживаются.

Установка

Чтобы использовать библиотеку в своем C++ проекте, ее нужно подключить как CMake подпроект:

add_subdirectory(bjo)
include_directories(bjo/include)
target_link_libraries(${PROJECT_NAME} bjo)

И установить зависимости с помощью conan.

conan install <путь до conanfile.txt, который лежит в папке с библиотекой>

Что такое корутины?

Корутина - это функция, которая может приостановить свое выполнение на какое-то время или до наступления какого-то события, а после возобновиться с того места, где была приостановлена.

Корутины позволяют писать асинхронный код, который выглядит как синхронный. Это очень круто, честно.

Как написать корутину в boost::asio

  • Чтобы ваша функция стала корутиной, нужно чтобы она возвращала boost::asio::awaitable<ТИП ДАННЫХ> (на самом деле много чего другого, но на базовом уровне этого хватает). После этого, в ее теле станет возможно использовать такие ключевые слова как co_return и co_await. Но! уже нельзя использовать обычный return.
  • Чтобы запустить свою корутину, ее нужно создать(заспавнить) в executor-e. Для этого нужно написать следующий код:
boost::asio::co_spawn(
        экземпляр io_context, или другой executor из asio,
        вызов вашей корутины, 
        указать completion_token  
);

Для работы с c++20 корутинами используются 2 токена:

  • boost::asio::use_awaitable - для того, чтобы иметь возможность отпустить контекст, до тех пор, пока задача не будет выполнена
  • boost::asio::detached - отправить корутину в "свободное плавание". Заспавнить ее в исполнителе и никак не дожидаться ее завершения

Основные компоненты фреймворка:

bjo::TaskProcessor

Важнейшим классом библиотеки является TaskProcessor, который представляет собой обертку над boost::asio::io_context, позволяющую исполнять асинхронные задачи в собственном пуле потоков, не блокируя вызвавший поток.

Зачем это нужно?

Иногда в асинхронном приложении может возникать необходимость запустить сложные CPU-bound вычисления, или обратиться к БД. Возникает проблема. Например, если запустить такую операцию в потоке, принимающем новые соединения (предположим, что мы пишем http-сервер) мы заблокируемся до окончания тяжелой операции. Это чревато тем, что мы перестанем обслуживать клиентов на какое-то время.

Исполнение задач в разных TaskProcessor-ах решает данную проблему. Пользователь библиотеки может определить TaskProcessor-ы на типы решаемых задач и запускать таски(корутины, коллбэки, футуры) в соответствующих Executor-ах.

Что ещё?

Пользователь может выбрать то, каким образом запустить асинхронную задачу. Можно использовать как стандартные корутины, так и std::future или классические callback-и.

Пример использования:

// Run task in detached mode. Don't wait completion.
 task_processor_.ProcessTask(Listen(), asio::detached); 

// When we need to perform task in another executor and fetch results.
auto result =  co_await task_processor_.ProcessTask(Coro(), asio::use_awaitable);

// using futures
std::future<Тип> res = task_processor_.ProcessTask(FutureTask(), asio::use_future);
// to get results type res.get() 

bjo::http::Server

bjo предоставляет пользователю удобную и эффективную высокоуровневую абстракцию, позволяющую реализовывать http серверы.

Как этим пользоваться?

Для того чтобы запустить http::Server в bjo нужно:

  • Задать обработчики на соответствующие таргеты
  • Зарегистрировать их в сервере
  • Запустить сервер

ПРИМЕР СОЗДАНИЯ СЕРВЕРА:

namespace asio = boost::asio;
namespace http = bjo::http;

  http::Server server(http::server::DefaultConfig());
  
  server.RegisterHandlers()
      (http::METHODS::GET, http::server::Route<"/">(), AsClass{})
      (http::METHODS::GET | http::METHODS::POST, http::server::Route<"/users/id=[0-9]+">(), AsFunc)
      (http::METHODS::GET, http::server::Route<"/values">(),
        [](const http::Request& req) -> asio::awaitable<http::Response> {
          // Handler can be implemented as lambda
        }
      )
  ;
  
  server.Serve(); // starts server

ВАЖНО

  • Обработчик http запроса должен иметь сигнатуру вида asio::awaitable< http::Response > (const http::Request& req)
  • Объект типа http::server::Route принимает шаблонным параметром регулярное выражение, описывающее паттерн, с которым будет сравниваться роут из заголовка

ПРИМЕР ОБРАБОТЧИКА

namespace asio = boost::asio;
namespace http = bjo::http;

// As Function
asio::awaitable<http::Response> AsFunction(const http::Request& req);


// As Lambda, that returns asio::awaitable<bjo::http::Response>
[](const http::Request& req) -> asio::awaitable<http::Response> {} 


// As Class
struct Handler {
    asio::awaitable<http::Response> operator()(const http::Request& req) {} 
};

bjo::http::Request и bjo::http::Response

Классы bjo::http::Request и bjo::http::Response - это alias-ы для реализаций из boost::beast.