Русский
Русский
English
Статистика
Реклама

Проект arataga реальный пример использования SObjectizer и RESTinio для работы с большим количеством HTTP-соединений

В последние 4.5 года я много рассказывал на Хабре про такие OpenSource проекты, как SObjectizer и RESTinio. Но вот об использовании SObjectizer и/или RESTinio в реальных проектах пока еще ни разу не удавалось поговорить (была лишь одна статья от стороннего автора).

Причина простая: мы не можем обсуждать те проекты, в которых мы сами применяли SObjectizer/RESTinio, ибо NDA. Равно как и не можем рассказывать о тех чужих проектах, о которых узнали в частном общении. Так что с наглядными примерами использования SObjectizer/RESTinio в реальной жизни всегда была напряженка.

Дабы как-то улучшить ситуацию пару лет назад мы даже сделали небольшой демо-проект Shrimp и опубликовали здесь серию статей о нем (раз, два, три). Но все-таки это было не более чем демонстрация.

К счастью или к несчастью, но далеко не самый удачный 2020-й год предоставил нам возможность показать как же выглядит реальный проект, в разработке которого SObjectizer и RESTinio активно используются. И в данной статье я попробую рассказать о том, как и для чего SObjectizer и RESTinio применяются в arataga, исходники которого можно найти на GitHub.

Тем, кто хочет больше узнать об arataga и причинах его появления на GitHub-е, рекомендую прочитать этот блог-пост. Здесь же в двух словах скажу лишь, что arataga -- это socks5+http/1.1 прокси-сервер, который затачивался под использование в следующих условиях (перечислены те из них, которые актуальны с точки зрения внутренней архитектуры arataga):

  • много точек входа, счет идет на тысячи (8 тысяч нужно было поддерживать сразу). У каждой точки входа уникальное сочетание IP+port;

  • подключения на одну точку входа могут идти с темпом в несколько десятков в секунду, параллельно на одной точке входа могут "висеть" сотни подключений;

  • одновременно могут существовать десятки тысяч подключений, общий темп появления новых подключений на всех точках входа может быть от 1500 в секунду;

  • аутентификация клиентов должна укладываться в единицы миллисекунд;

  • обновленная конфигурация и новые списки пользователей, которым разрешена работа с прокси-сервером, периодически доставляется через HTTP POST на специальный административный HTTP-вход.

Далее в статье я сосредоточусь на архитектурных и технических моментах, не затрагивая тему "зачем это все было нужно?"

Вероятно, для того, чтобы лучше понимать изложенное ниже, нужно иметь общие представления о SObjectizer-овских агентах, диспетчерах и почтовых ящиках (mbox-ах). Старая обзорная статья может в этом помочь (хотя сам SObjectizer за это время несколько изменился).

Тезисно о принятых проектных и архитектурных решениях

Многопроцессность или многопоточность?

Прежде всего нам предстояло сделать выбор между реализацией arataga в виде мультипроцессного или мультипоточного приложения.

В случае мультипроцессного приложения потребовалось бы иметь процесс-родитель, который бы запускал N дочерних процессов-исполнителей. Процесс-родитель отвечал бы за управление дочерними процессами и за распределение конфигурации/списков пользователей между дочерними процессами. Дочерние же процессы открывали бы точки входа и обслуживали бы входящие соединения от клиентов и исходящие к целевым хостам.

Каждый процесс при этом мог бы быть простым однопоточным процессом (может быть за исключением родительского, в котором могло бы потребоваться больше одного рабочего потока).

Достаточно простая и надежная схема, почти что в духе unix way. Но связываться с ней не очень хотелось из-за большого количества IPC-взаимодействия между родительским и дочерними процессами: в одну сторону должна идти управляющая информация, в другую -- диагностическая.

Вариант в виде многопоточного приложения выглядел проще, т.к. все взаимодействующие сущности оказывались в рамках одного общего адресного пространства и распространение любой информации в любых направлениях не представляло вообще никакой проблемы.

Плюс к тому, у нас в руках был большой молоток, SObjectizer, с помощью которого разрабатывать многопоточные приложения гораздо проще, чем при ручной работе с std::thread, std::mutex и std::condition_variable и т.п. Если выразить основные сущности приложения в виде SObjectizer-овских агентов, распределить этих агентов должным образом по рабочим нитям и организовать их взаимодействие на базе асинхронных сообщений, то ужасы многопоточности можно спокойно обойти стороной. Что, собственно, в очередной раз и произошло.

Сколько рабочих потоков нужно?

Здесь все было понятно изначально, еще даже до начала разработки arataga, поскольку старый прокси-сервер, на замену которого arataga и разрабатывался, использовал модель thread-per-connection. Когда счет одновременно живущих соединений идет на десятки тысяч, то thread-per-connection не есть хорошо.

Поэтому об использовании в arataga схемы thread-per-connection речь не шла вообще. Сразу же был взят курс на применение чего-то похожего на thread-per-core.

Было решено под обслуживание подключений отводить не все доступные вычислительные ядра, а использовать формулу (nCPU-2), где nCPU -- это количество ядер.

Так, если на машине 8 ядер, то в arataga будет создано 6 рабочих потоков, которые будут отвечать за обслуживание соединений. Т.е. работой с трафиком будут заняты 6 ядер. Два оставшихся ядра будут доступны для других рабочих потоков внутри arataga. Плюс и самой ОС нужно что-то оставить, а то не очень приятно удаленно подключаться к работающему серверу по ssh, когда у него все ядра загружены под 100%

Что будет агентом, а что не будет?

Изначально в работе arataga выделялось несколько типов операций, которые должны были выполняться на разных рабочих контекстах:

  • разбор конфигурации. Обработка конфигурации для нескольких тысяч точек входа может занимать некоторое время (например, 5ms). Сюда входит и парсинг/валидация конфигурации, и сохранение ее локальной копии, и удаление устаревших точек входа, которых нет в новой конфигурации, и создание новых точек входа, добавленных в обновленной конфигурации;

  • разбор списка пользователей и формирование структур для представления этого списка в памяти. Это также может занимать некоторое время, поскольку списки пользователей могут насчитывать несколько десятков тысяч строк;

  • обработка точек входа. Т.е. создание серверных сокетов на заданных IP+port, прием новых подключений с учетом ограничений на максимальное количество параллельных подключений и т.д.;

  • обслуживание принятых подключений. Т.е. вычитывание данных от клиента, определение типа протокола, осуществление проксирования подключения в зависимости от типа протокола и команды, пришедшей от клиента.

Первые три операции явно напрашивались на то, чтобы быть представленными в виде отдельных агентов, которые привязываются к тому или иному диспетчеру.

А вот по поводу последнего, т.е. обслуживания уже принятых подключений, можно было поступить по-разному. Можно было попытаться представить каждое подключение в виде отдельного агента.

Но этот подход меня лично не вдохновлял по нескольким причинам.

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

Во-вторых, не очень понятно, в чем смысл использовать здесь агента. Взаимодействие с сетью предполагалось делать с использованием Asio и асинхронных операций. Т.е., если вызывается Asio-шный async_read_some, то когда-то Asio вызовет для этой операции completion_handler. И если мне нужно передать результат async_read_some агенту, то в completion_handler нужно будет отослать агенту сообщение, которое агент обработает. Что, вообще-то говоря, не бесплатно, т.к. сперва на каком-то io_context будет запланирован вызов completion_handler, а затем, когда до completion_handler дойдет очередь, будет запланирован вызов обработчика события у агента. И только затем когда-то будет вызван этот самый обработчик. Что наводит на мысль "а не слишком ли много приходится платить за концептуальную чистоту?" Тем более, что по прошлому опыту я знал, что когда операции чтения/записи сокета доставляются до агента в виде сообщений, то как-то кардинально это работу с сетью не упрощает. Может быть даже наоборот.

Добавим сюда еще и то, что регистрация/дерегистрация агентов в SObjectizer все-таки не самая дешевая операция (это связано с механизмом коопераций и гарантиями транзакционности операции регистрации). Конечно, если принимать новые подключения с темпом в 2000-3000 в секунду, то "тормоза" SObjectizer-а здесь не проявятся, SObjectizer может регистрировать/дерегистрировать кооперации с гораздо более высоким темпом. Но все-таки у частого создания/удаления агентов есть своя цена, и если ее можно не платить, то лучше ее не платить.

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

Для чего планировалось использовать RESTinio?

Изначально предполагалось, что посредством RESTinio будет реализован административный HTTP-вход. Причем не в виде SObjectizer-овского агента, а просто единственная отдельная нить, на которой будет работать RESTinio-сервер.

Библиотека RESTinio, действительно, была использована таким очевидным образом. А вот о паре менее очевидных способов применения RESTinio в arataga речь пойдет ниже.

Погрузимся в подробности

Некоторые детали использования SObjectizer

В этой части бегло пройдемся по некоторым моментам использования SObjectizer в коде arataga. Выбор чисто субъективный, мне показалось, что именно они наиболее показательны и/или не обычны.

В рамках одной статьи невозможно обсудить все аспекты применения SObjectizer в проекте arataga, поэтому что-то неизбежно останется "за кадром". Так что если у кого-то из читателей возникнут вопросы, то постараюсь ответить в комментариях.

Агент startup_manager и "глобальный таймер"

Работа arataga начинается с запуска агента startup_manager. Этот агент отвечает за последовательный запуск основных "компонентов" arataga: сперва стартует агент user_list_processor, затем агент config_processor, затем уже запускается отдельная нить с RESTinio-сервером для административного HTTP-входа.

Каждый из запускаемых startup_manager агентов должен затем подтвердить свой успешный запуск. Если этого не произошло за отведенное время, то startup_manager прерывает работу приложения вообще.

В агенте startup_manager можно обнаружить один непривычный трюк: запуск при старте периодического сообщения one_second_timer_t, которое отсылается раз в секунду на специальный mbox.

В какой-то мере это преждевременная оптимизация.

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

Обычно это выполняется посредством отложенного сообщения: агент отсылает самому себе отложенное на 1 секунду сообщение, а когда оно приходит, то проверяет времена жизни подключений, времена отсутствия активностей в них и т.д. После чего шлет себе следующее отложенное сообщение. Или же вместо перепосылки отложенных сообщений один раз запускает периодическое сообщение.

Но когда агентов, нуждающихся в подобных периодических проверках, будут тысячи, то это будет означать, что раз в секунду будут порождаться тысячи отложенных/периодических сообщений.

Ну и зачем, спрашивается, это делать, если достаточно всего одного сообщения, которое будет отсылаться в отдельный mbox один раз в секунду? И на которое смогут подписаться столько получателей, сколько потребуется.

Вот как раз для того, чтобы каждый агент, обслуживающий точку входа, не запускал свое собственное периодическое сообщение для контроля тайм-аутов, агент startup_manager делает это посредством one_second_timer_t.

Понятие io_thread и ее реализация на базе asio_one_thread-диспетчера

Выше говорилось, что в реализации arataga было решено придерживаться модели, похожей на thread-per-core, при этом (nCPU-2) рабочих потока будет выделено под выполнение операций с сетью. Такие рабочие нити получили условное название io_thread (далее io_thread == "I/O нить" и поэтому "io_thread" будет иметь женский род).

Т.к. работа с сетью в arataga работа идет посредством Asio, то для следования идее thread-per-core было решено сделать так, чтобы на каждой io_thread работал свой собственный экземпляр asio::io_context.

Т.е. нам нужна была рабочая нить, которая создает экземпляр asio::io_context, а затем вызывает для него run(). После чего на этой нити нужно было бы еще как-то разместить и агентов, реализующих точки входа. И чтобы одна и та же нить обслуживала и операции Asio, и события SObjectizer-овских агентов.

В arataga для этих целей просто используются экземпляры asio_one_thread-диспетчера из so5extra. Каждый такой диспетчер -- это отдельный рабочий поток (т.е. io_thread).

Агент config_processor, о котором речь пойдет ниже, создает N экземпляров asio_one_thread-диспетчеров, а затем просто привязывает к этим экземплярам агентов, обслуживающих точки входа.

Агенты authentificator и dns_resolver, их дублирование на каждой io_thread

Подключающихся к arataga клиентов нужно аутентифицировать. Эта задача была поручена отдельному агенту authentificator. Когда агенту, обслуживающему точку входа, нужно аутентифицировать клиента, то отсылается сообщение агенту authentificator, который выполняет аутентификацию и шлет назад результат аутентификации.

Еще агентам, которые обслуживают точки входа, нужно выполнять резолвинг доменных имен. За эту операцию отвечает еще один специальный агент -- dns_resolver. Тут используется подобная схема: агент-точка-входа отсылает агенту dns_resolver сообщение, а тот присылает назад результат поиска доменного имени.

Мне показалось, что если уж мы пытаемся следовать модели thread-per-core, то логичным было бы сделать отдельную копию агентов dns_resolver и authentificator для каждой из io_thread. Поэтому эти агенты создаются сразу же вслед за очередным asio_one_thread-диспетчером и привязываются к только что созданному диспетчеру.

Распределение агентов по диспетчерам

В результате получилась следующая картинка:

У агентов config_processor и user_list_processor есть собственные рабочие потоки, которые реализуются посредством штатного диспетчера SObjectizer под названием one_thread.

Каждая io_thread представлена отдельным диспетчером asio_one_thread из so5extra. И на каждом таком диспетчере работает сразу несколько (десятков, сотен, тысяч) агентов acl_handler.

Агент config_processor

Агент config_processor отвечает за обработку конфигурации arataga, поддержание списка существующих точек входа, их распределение между io_threads, и за создание/удаление точек входа при изменении конфигурации.

При своем старте config_processor пытается прочитать локальную копию конфигурационного файла. Если это удалось, то config_processor принимает поднятую из конфига информацию за текущую конфигурацию и создает описанные в ней точки входа (регистрирует агентов acl_handler).

Если же локальной копии конфигурации нет или же прочитать ее не удалось, то config_processor ждет, пока придет новая конфигурация от административного HTTP-входа.

Когда от административного HTTP-входа поступает новая конфигурация, что после ее успешной обработки config_processor рассылает уведомления об изменении параметров arataga на отдельный multi-producer/multi-consumer mbox. Так изменения в конфигурации становятся доступны всем, кто в них заинтересован.

В работе config_processor с acl_handler есть важная особенность: сейчас в arataga агент acl_handler намертво привязывается к конкретному asio_one_thread и живет на этом asio_one_thread до тех пор, пока не будет удален.

Эта особенность может привести к ситуации, когда из конфигурации удалили, скажем, 1000 точек входа, и в результате на одной из io_threads осталось заметно меньше работающих агентов, чем на других io_threads.

Эта диспропорция может быть устранена при добавлении новых точек входа. В этом случае config_processor при создании новых агентов acl_handler будет привязывать их к тем диспетчерам asio_one_thread, на которых сейчас меньше всего живых acl_handler.

Но если новые точки входа не создаются, то диспропорция останется и config_processor не будет перераспределять старые acl_handler между диспетчерами asio_one_thread. По двум причинам:

  • во-первых, в SObjectizer нет возможности перепривязать агента с одного диспетчера на другой. Поэтому к какому диспетчеру агента изначально привязали, на таком он и будет продолжать работать;

  • во-вторых, в каждом агенте acl_handler может существовать множество Asio-шных объектов, завязанных на конкретный экземпляр asio::io_context. Если перемещать содержимое acl_handler с одной io_thread на другую, то нужно будет и переконструировать эти Asio-шные объекты.

Чтобы не возиться со всем этим добром было решено смириться с такими диспропорциями в первой версии aragata. Может на практике они вообще не будут столь серьезными, чтобы оказывать влияние на производительность. Ну а если таки будут, вот тогда можно было бы и озадачиться проблемой миграции точки входа с одной io_thread на другую.

Агент user_list_processor

Агент user_list_processor отвечает за работу со списком пользователей.

При своем старте user_list_processor пытается прочитать локальную копию списка пользователей. Если это удалось, то локальная копия становится актуальным списком. Если не удалось, то user_list_processor ждет поступления нового списка от административного HTTP-входа.

Когда от административного HTTP-входа прилетает новый список пользователей, то user_list_processor пытается его обработать. И, если это получается успешно, то обновленный список рассылается на отдельный multi-producer/multi-consumer mbox на которые подписаны агенты authentificator. Что позволяет authentificator-ам получать обновленные списки пользователей сразу после того, как user_list_processor завершит их обработку.

Агент acl_handler

Агент acl_handler является, наверное, самым сложным и объемным агентом в arataga (hpp-файл, cpp-файл). Его задача -- это создать серверный сокет для точки входа, принимать и обслуживать подключения к этому серверному сокету.

В связи с этим в рамках данной статьи можно выделить несколько аспектов в реализации acl_handler.

Во-первых, acl_handler инициирует асинхронные I/O операции с помощью Asio, в частности, вызывает async_accept у Asio-шного asio::ip::tcp::acceptor. В async_accept передается completion-handler в виде лямбды, где напрямую вызываются методы агента. Такие вызовы безопасны потому, что агент привязан к asio_one_thread диспетчеру. Этот диспетчер специально создан для того, чтобы на одном рабочем контексте можно было работать и с агентами, и с Asio. Если бы acl_handler привязывался к какому-то другому диспетчеру (например, к штатному one_thread-диспетчеру), то этого делать было бы нельзя.

Во-вторых, логика у acl_handler достаточно тривиальная, но даже и здесь нашлось место для применения иерархического конечного автомата (которые в SObjectizer-е уже довольно давно поддерживаются):

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

В-третьих, агент acl_handler не занимается сам обслуживанием принятых подключений и обработкой протоколов socks5/http. Вместо этого для каждого соединения создается т.н. connection_handler. Это объект, в который отдается принятое подключение и который уже с этим подключением непосредственно работает. Т.е. читает из него данные, пытается определить протокол, пытается обслуживать подключение согласно того или иного протокола.

Т.е. когда в результате async_connect у acl_handler появляется новый asio::ip::tcp::socket, то создается объект, реализующий интерфейс connection_handler_t, и новый socket отдается этому только что созданному connection_handler-у. Больше про новое подключение acl_handler ничего не знает. Далее acl_handler лишь хранит у себя умный указатель на connection_handler и лишь время от времени дергает у connection_handler-а метод on_timer (тот самый "глобальный таймер" о котором речь шла выше). А вот connection_handler дальше живет своей собственной и весьма непростой жизнью.

Вообще говоря, как раз тот кусок arataga, который относится к connection_handler-ам, является самым мудреным в проекте. Вероятно, далеко не самым удачным. И, скорее всего, именно он мог бы стать первым кандидатом на рефакторинг по итогам опытной эксплуатации. Поскольку первая версия arataga создавалась в высоком темпе, в условиях сжатых сроков, то ничего лучше сходу придумать не удалось. Возможно, здесь следовало бы применить stackful-короутины. Но экспериментировать с ними не было времени. Так что, если история с arataga получит продолжение (что вряд ли, но вдруг), к этому куску arataga нужно будет сделать еще один, более вдумчивый подход. Пока что получилось то, что получилось :(

Использование retained_mbox для распространения конфигурации

Примечательным моментом использования SObjectizer-овских mbox-ов в arataga является применение retained_mbox из so5extra для распространения информации о текущей конфигурации и списках пользователей.

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

В arataga это свойство retained_mbox используется для того, чтобы агенты authentificator сразу же могли получить текущий список пользователей. Сценарий работы такой:

  • стартует агент user_list_processor. Он читает локальную копию списка пользователей и отсылает сообщение с этим списком в retained_mbox. На этом этапе подписчиков у retained_mbox еще нет, но нас это не волнует;

  • затем стартует агент config_processor, который читает локальную копию конфигурации и создает io_threads вместе с authentificator-ами;

  • каждый запущенный authentificator должен получить список пользователей, для чего authentificator-ы подписываются на соответствующее сообщение из retained_mbox-а;

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

Благодаря этому authentificator-у при старте не нужно ни у кого ничего явно запрашивать.

Правда, платить за это нужно тем, что authentificator вынужден делать подписку в so_evt_start, а не в предназначенном для этого so_define_agent.

Некоторые детали использования RESTinio

Теперь попробуем пробежаться по нескольким аспектам применения RESTinio в arataga. Тем более, что одно из применений было очевидным, тогда как одно совсем не очевидным.

Первое применение RESTinio, самое очевидное: административный HTTP-вход

Очевидное применение, ради которого RESTinio и подтягивался в проект arataga, -- это реализация административного HTTP-входа. Как раз здесь простота асинхронной обработки входящих запросов посредством RESTinio и была нужна с самого начала.

В этом месте RESTinio используется классическим способом. Запускается RESTinio-сервер, этот сервер принимает входящие запросы, выполняет их первичную валидацию. После чего пересылает запрос во внутрь arataga и ждет ответа. Когда ответ сформирован, то RESTinio отсылает его клиенту.

Упомянуть здесь можно разве лишь то, что для взаимодействия RESTinio- и SObjectizer-частей приложения были сделаны вспомогательные интерфейсы (раз, два). Когда startup_manager запускает RESTinio-сервер, то отдает серверу реализацию интерфейса requests_mailbox_t. RESTinio-сервер через этот интерфейс передает в SObjectizer-часть arataga входящие запросы. И каждый входящий запрос сопровождается реализацией интерфейса replier_t. Посредством этого интерфейса реальные обработчики запроса могут отвечать RESTinio-серверу.

Два эти интерфейса скрывают от RESTinio-части существование SObjectizer-части arataga и наоборот. Что мне показалось полезным. Хотя бы плане сокращения времени компиляции отдельных cpp-файлов.

Второе применение RESTinio, не очевидное, но вполне ожидаемое: работа с HTTP-заголовками при проксировании HTTP-соединений

Второе применение стало для меня лично неожиданным, хотя оно было вполне себе логичным. Дело в том, что применить RESTinio в его текущем виде для проксирования HTTP-соединений не представляется возможным в силу нынешних архитектурных особенностей RESTinio.

Поэтому работу с проксируемыми HTTP-соединениями в arataga пришлось делать "с нуля". И одной из составляющей этой работы является разбирательство с HTTP-заголовками в запросах/ответах, а также с содержимым некоторых из них.

И вот когда у меня руки дошли до обработки HTTP-заголовков в проксируемых соединениях, то я внезапно (c) осознал, что в RESTinio уже есть инструментарий для этого. Чем и воспользовался, хотя изначально о таком даже и не думал.

Третье применение RESTinio, совсем не очевидное: синтаксический разбор конфигурации

А вот третье, совсем не очевидное, применение RESTinio не то, чтобы было сразу мной задумано. Но такой вариант начал просматриваться чуть ли не с самого начала.

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

Плюс к тому в этот синтаксис хотелось внести небольшие, но важные с точки зрения простоты использования, дополнения. Вроде суффиксов, которые бы обозначали объем данных или времена таймаутов. Например:

acl.io.chunk_size 16kib # Вместо 16384timeout.authentification 1200ms # Вместо 1200timeout.connect_target 7s # Вместо 7000

Ну и для того, чтобы не делать парсинг конфигов вручную только штатными средствами стандартной библиотеки C++ (очень куцей в этом плане), нужно было задействовать какой-то генератор парсеров. А зачем тащить в проект еще что-то, если нечто подобное уже есть в RESTinio?

Так что easy_parser из RESTinio, который изначально появился в RESTinio для упрощения разбора HTTP-заголовков, пригодился в arataga и для разбора конфигурации.

Любопытный, на мой взгляд, пример использования easy_parser-а из RESTinio можно найти здесь. Именно этот transfer_speed_p() используется для реализации команд конфигурации вроде:

bandlim.in 850kibbandlim.out 700kib

Заключение

Реальные проекты, подобные arataga, являются отличным полигоном, на котором разработчик библиотеки может увидеть насколько удобна его библиотека вне тепличных условий уютного манямирка. Как правило, при использовании библиотеки в новом проекте отлично проявляются просчеты в дизайне библиотеки и/или же обнажаются подводные камни, о которых ты пока что даже и не подозревал. Из чего затем вырастают вполне себе конкретные идеи о том, куда следует двигаться дальше.

С RESTinio это сработало на 100%. Опыт применения RESTinio в arataga дал пинка под за толчок в сторону разработки пусть приблизительного, но аналога middleware из ExpressJS. Первый результат уже доступен в 0.6.13. И, если пойти на слом API в ветке 0.7, то можно будет поддержать и цепочки асинхронных обработчиков.

Но еще более важным итогом использования RESTinio в arataga стало изменение лично моих взглядов на позиционирование RESTinio в мире аналогичных фреймворков для C++.

Так что для RESTinio испытание arataga оказалось исключительно полезным.

А вот с SObjectizer-ом получилось скучно и обыденно. Он просто работал. Ничего не пришлось подкручивать или добавлять. Кой-какие мысли о потенциально привлекательных направлениях дальнейшего движения появились. Но пока не вполне оформившиеся и не имеющие однозначно четкого подтверждения того, что это реально необходимо делать.

Я же надеюсь, что данная статья даст читателям, которые с любопытством посматривают на RESTinio и/или SObjectizer(+so5extra), но пока что сами эти проекты не пробовали, возможность взглянуть на реальный код, написанный с применением наших инструментов. И сделать собственные выводы о том, нравится ли увиденное или нет.

Что касается перспектив самих RESTinio и SObjectizer/so5extra, то тут все не так однозначно и без внешней поддержки, боюсь, их развитие приостановится. Впрочем, это уже совсем другая история

Источник: habr.com
К списку статей
Опубликовано: 18.01.2021 12:13:17
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Open source

Программирование

C++

Cpp

Cplusplus

Sobjectizer

Restinio

Actor model

Http-сервер

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru