Информационный портал по безопасности » Компании » Microsoft » Новая функциональность в RESTinio и опять с помощью C++ных шаблонов

 

Новая функциональность в RESTinio и опять с помощью C++ных шаблонов

Автор: admin от 13-11-2020, 08:06, посмотрело: 20

Увидело свет очередное обновление небольшой библиотеки для встраивания асинхронного HTTP-сервера в C++ приложения: RESTinio-0.6.12. Хороший повод рассказать о том, как в этой версии с помощью C++ных шаблонов был реализован принцип "не платишь за то, что не используешь".



Новая функциональность в RESTinio и опять с помощью C++ных шаблонов

Заодно в очередной раз можно напомнить о RESTinio, т.к. временами складывается ощущение, что многие C++ники думают, что для встраивания HTTP-сервера в современном C++ есть только Boost.Beast. Что несколько не так, а список существующих и заслуживающих внимания альтернатив приведен в конце статьи.



О чем речь пойдет сегодня?



Изначально библиотека RESTinio никак не ограничивала количество подключений к серверу. Поэтому RESTinio, приняв очередное новое входящее подключение, сразу же делала новый вызов accept() для принятия следующего. Так что если вдруг на какой-то RESTinio-сервер придет сразу 100500 подключений, то RESTinio не заморачиваясь постарается принять их все.



На такое поведение до сих пор никто не жаловался. Но в wish-list-е фича по ограничению принимаемых подключений маячила. Вот дошли руки и до нее.



В реализации были использованы C++ные шаблоны, посредством которых выбирается код, который должен или не должен использоваться. Об этом-то мы сегодня и поговорим.

шаблонного класса, который параметризуется типом mutex. А нужная реализация выбирается благодаря специализации шаблона:

template< typename Strand >
class connection_count_limiter_t;

template<>
class connection_count_limiter_t< noop_strand_t >
   :  public connection_count_limits::impl::actual_limiter_t< null_mutex_t >
{
   using base_t = connection_count_limits::impl::actual_limiter_t< null_mutex_t >;

public:
   using base_t::base_t;
};

template<>
class connection_count_limiter_t< default_strand_t >
   :  public connection_count_limits::impl::actual_limiter_t< std::mutex >
{
   using base_t = connection_count_limits::impl::actual_limiter_t< std::mutex >;

public:
   using base_t::base_t;
};


Тип strand-а задается в traits, поэтому достаточно параметризовать connection_count_limiter_t типом traits::strand_t и автоматически получается либо версия для однопоточного, либо версия для многопоточного режимов.



Экземпляр connection_count_limiter-а теперь содержится в объекте Acceptor и Acceptor обращается к этому connection_count_limiter-у для того, чтобы узнать, можно ли делать очередной вызов accept. А connection_count_limiter либо разрешает вызвать accept, либо нет.



Объект connection_count_limiter получает уведомления от разрушаемых объектов Connection. Если connection_count_limiter видит, что вызовы accept были заблокированы, а сейчас появилась возможность возобновить прием новых подключений, то connection_count_limiter отсылает нотификацию Acceptor-у. И получив эту нотификацию Acceptor возобновляет вызовы accept.



А уведомления о разрушении объектов Connection к connection_count_limiter приходят благодаря объектам connection_lifetime_monitor, о которых речь пойдет дальше.



Актуальный connection_lifetime_monitor



В Acceptor-е есть connection_count_limiter который должен узнавать о моментах разрушения объектов Connection.



Очевидным решением было бы реализовать информирование connection_count_limiter-а прямо в деструкторе Connection. Но дело в том, что в RESTinio Connection может преобразовываться в WS_Connection в случае перевода соединения в режим WebSocket-а. Так что аналогичное информирование потребовалось бы делать и в деструкторе WS_Connection-а.



Дублировать же одну и ту же функциональность в двух разных местах не хотелось, поэтому задача информирования connection_count_limiter-а об исчезновении подключения была делегирована новому объекту connection_lifetime_monitor.



Это Noncopyable, но зато Movable объект, который создается внутри Connection. Соответственно, и разрушается он вместе с объектом Connection.



Если же Connection преобразуется в WS_Connection, то экземпляр connection_lifetime_monitor перемещается из Connection в WS_Connection. И затем разрушается уже вместе с владеющим WS_Connection.



Т.е. итоговая схема такая:




  • в Acceptor-е живет connection_count_limiter;

  • когда Acceptor принимает новое подключение, то вместе с новым Connection создается и новый экземпляр connection_lifetime_monitor;

  • когда Connection умирает, то разрушается и connection_lifetime_monitor;

  • умирающий connection_lifetime_monitor информирует connection_count_limiter о том, что количество соединений уменьшилось.



Если Connection преобразуется в WS_Connection, то ничего принципиально не меняется, просто актуальную информацию о живом соединении начинает держать у себя connection_lifetime_monitor из WS_Connection.



Подчеркнем, что connection_lifetime_monitor вынужден держать у себя внутри указатель на connection_count_limiter. Иначе он не сможет дернуть connection_count_limiter при своем разрушении.



Фиктивные connection_count_limiter и connection_lifetime_monitor



Выше было показано, что стоит за connection_count_limiter и connection_lifetime_monitor в случае, когда ограничение на количество подключений задано.



Если же пользователь задает use_connection_count_limiter равным false, то понятия connection_count_limiter и connection_lifetime_monitor остаются. Но теперь это фиктивные connection_count_limiter и connection_lifetime_monitor, которые, по сути, ничего не делают. Например, фиктивный connection_lifetime_monitor ничего внутри себя не хранит.



Тем не менее, внутри Acceptor-а все еще живет экземпляр connection_count_limiter, пусть даже и фиктивный. А внутри Connection (и WS_Connection) есть пустой connection_lifetime_monitor.



Можно было, конечно, попробовать упороться шаблонами по полной программе и постараться избавиться от присутствия пустого connection_lifetime_monitor в Connection. Но, имхо, наличие лишнего байта в Connection (WS_Connection) не стоит сложности кода, который позволяет от этого байта избавиться. Тем более, что в C++20 добавили атрибут no_unique_address, так что со временем эта проблема должна решиться гораздо более простым и наглядным способом. Впрочем, если для кого-то дополнительный байт в Connection — это реальная проблема, то откройте Issue, будем ее решать :)



Выбор подходящих connection_count_limiter и connection_lifetime_monitor



После того, как появились актуальный и фиктивные реализации connection_count_limiter и connection_lifetime_monitor осталось научиться выбирать между ними в зависимости от содержимого traits. Делается это так:



template< typename Traits >
struct connection_count_limit_types
{
   using limiter_t = typename std::conditional
      <
         Traits::use_connection_count_limiter,
         connection_count_limits::connection_count_limiter_t<
               typename Traits::strand_t >,
         connection_count_limits::noop_connection_count_limiter_t
      >::type;

   using lifetime_monitor_t =
         connection_count_limits::connection_lifetime_monitor_t< limiter_t >;
};


Т.е. для того, чтобы получить актуальный тип connection_count_limiter-а достаточно написать что-то вроде:



typename connection_count_limit_types<traits>::limiter_t


Хранение ограничения на количество подключений в server_settings



Осталось рассмотреть еще один небольшой момент: параметры для RESTinio сервера хранятся в server_settings_t и, по хорошему, надо бы сделать так, чтобы ограничение на количество подключений нельзя было задавать, если в traits use_connection_count_limiter выставлен в false.



Тут используется фокус, к которому мы уже прибегали раньше:




  • создается шаблонный тип, который должен использоваться в качестве примеси (mixin);

  • у этого шаблонного типа есть специализация для фиктивного connection_count_limiter-а;

  • этот шаблонный тип подмешивается в качестве базы в server_settings_t.



В самом же server_settings_t делается метод max_parallel_connections, который содержит внутри static_assert. Этот static_assert ведет к ошибке компиляции, если в Traits запрещено использовать ограничение на количество подключений. Такой подход, имхо, ведет к более понятным сообщениям об ошибках, нежели отсутствие метода max_parallel_connections когда use_connection_count_limiter равен false.



Вместо заключения



RESTinio продолжает развивается по мере наших сил и возможностей. Некоторые планы по дальнейшему развитию есть. Но как-то углубляться в них не хочется из-за суеверных соображений. Уж очень жизненным оказывается афоризм про озвучивание планов и Господа Бога. Такое ощущение, что он срабатывает в 99% случаев :)



Что можно точно сказать, так это то, что мы внимательно прислушиваемся к пожеланиям. Если вам чего-то не хватает в RESTinio, то расскажите нам об этом. Либо прямо здесь, в комментариях, либо на GitHub-е через Issues.



HTTP-клиент в RESTinio?



Время от время мы сталкиваемся с сожалениями потенциальных пользователей о том, что RESTinio реализует только сервер, но не имеет функциональности HTTP-клиента.



Тут все просто. Мы делали RESTinio под конкретные сценарии использования. И это были сценарии использования RESTinio для реализации HTTP-входа в C++ приложения. Клиент нам не был нужен.



Вероятно, реализация клиента в RESTinio может быть добавлена.



Вероятно.



С определенностью сложно сказать, т.к. эту тему мы никогда глубоко не прорабатывали. Если бы кто-то рискнул профинансировать эту работу, то можно было бы всерьез за реализацию клиента взяться. Но за собственный счет мы этот объем просто не поднимем. Поэтому HTTP-клиента в RESTinio нет.



Bonus track: Так Boost.Beast-ом ли единым?



Действительно очень часто на просторах Интернета на вопрос "А что есть в C++ для реализации HTTP-сервера" отвечают Boost.Beast. К моему удивлению часто все еще вспоминают CROW, который уже несколько лет как мертв.



Какие-то другие варианты встречаются довольно редко. Хотя их не так уж и мало. Кроме нашего RESTinio имеет смысл упомянуть, как минимум, следующие разработки (в алфавитном порядке):





Ну и не забудем про возможности фреймворка POCO.



Так что есть из чего выбирать. И, если вам не нужна экстремальная производительность и тотальный контроль за всеми аспектами, плюс вы хотите обойтись минимумом усилий, то есть смысл сперва рассмотреть альтернативы Boost.Beast. Потому что Beast, при всех своих достоинствах, слишком уж низкоуровневый.



Источник: Хабр / Интересные публикации

Категория: Компании / Microsoft

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
  • bowtiesmilelaughingblushsmileyrelaxedsmirk
    heart_eyeskissing_heartkissing_closed_eyesflushedrelievedsatisfiedgrin
    winkstuck_out_tongue_winking_eyestuck_out_tongue_closed_eyesgrinningkissingstuck_out_tonguesleeping
    worriedfrowninganguishedopen_mouthgrimacingconfusedhushed
    expressionlessunamusedsweat_smilesweatdisappointed_relievedwearypensive
    disappointedconfoundedfearfulcold_sweatperseverecrysob
    joyastonishedscreamtired_faceangryragetriumph
    sleepyyummasksunglassesdizzy_faceimpsmiling_imp
    neutral_faceno_mouthinnocent