LINUX.ORG.RU

RESTinio-0.4.3 с поддержкой sendfile

 , ,


3

5

Мы обновили свою легковесную C++14 библиотеку для встраивания HTTP-входа в C++ приложения до версии 0.4.3.

Основные изменения в RESTinio со времени последнего анонса:

  • новые варианты restinio::run(), позволяющие задать внешний asio::io_context для RESTinio;
  • добавлена поддержка sendfile (https://stiffstream.com/en/docs/restinio/0.4/sendfile.html);
  • request_id теперь доступен внутри обработчика входящего HTTP-запроса;
  • плюс несколько мелких изменений и улучшений в коде самого RESTinio.

Библиотека живет на bitbucket-е (https://bitbucket.org/sobjectizerteam/restinio-0.4) c зеркалом на github-е (https://github.com/Stiffstream/restinio), документация доступна у нас на сайте (https://stiffstream.com/en/docs/restinio/0.4/). Распространяется под BSD-3-CLAUSE лицензией.

Мы создавали RESTinio для того, чтобы иметь возможность асинхронной обработки входящих запросов в случаях, когда для формирования ответа нужно обратиться к медленно отвечающему стороннему сервису. Иногда обращения к таким сторонним сервисам нужно делать посредством HTTP. Для таких целей широко используется Си-шная библиотека libcurl. Подружить асинхронную обработку входящих запросов посредством RESTinio с асинхронной обработкой исходящих запросов посредством libcurl можно несколькими способами. Подробнее эту тему мой коллега раскрыл в небольшой серии статей: часть 1, часть 2, часть 3.

Развитие RESTinio продолжается. У нас есть свои идеи о том, что можно было бы добавить в следующих версиях библиотеки. Но нам было бы очень интересно услышать пожелания от тех, кто смотрел на RESTinio, но еще не начал её использовать:

  • что бы вам хотелось увидеть в RESTinio?
  • чего не хотелось бы видеть?
  • что останавливает вас от использования RESTinio?

Ответ на: комментарий от eao197

Например это может потребоваться если есть i/o отличное от сетевого или уже есть в проекте нестандартный фреймворк для i/o(что бывает относительно часто) и хочется встроить только разбор http со своей логикой.

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

В частности ещё можно потешить чсв, т.к. таких решений на плюсах насколько я знаю нет в состоянии «живо и поддерживается».

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

Это не согласуется с тем, что мы хотим сделать. А хотим две простые вещи:

* минимум геморроя и забот для пользователя. В идеале пользователь описывает то, что ему нужно, а мы за него все остальное делаем. В частности, мы берем на себя заботу о контроле тайм-аутов на соединениях. Ну или добавление Express router-а в RESTinio — это из той же оперы;

* более чем приемлемую при этом производительность.

Пока что у нас это получается (хочется надеятся). И по простоте, и по производительности (результаты замеров от конца прошлого года: https://bitbucket.org/sobjectizerteam/restinio-benchmark).

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

Но вот так, чтобы RESTinio еще и работал с чужим event-loop. Это пока вне наших хотелок.

eao197 ★★★★★ ()
Ответ на: комментарий от pon4ik

RESTinio vs CROW

Из того что можно быстро посмотреть:

  • В CROW нет асинхронной обработки, т.е. когда вызывается обработчик запроса, то он должен вернуть ответ.
  • CROW нельзя крутить на внешнем event-loop, RESTinio же можно запустить на внешнем asio::io_context, на котором сожно гонять много чего еще, самый простой пример, запустить 2 (или n) серверов на одном event-loop.
  • в CROW нет поддержки файлов.
kola ()
Ответ на: комментарий от eao197

Но вот так, чтобы RESTinio еще и работал с чужим event-loop.

Тогда, имхо, его прикрутят в лучшем случае на старте или как ни странно к нечувствительному к производительности решению. А если необходимость взаимодействия с web появится в середине жизненного цикла продукта, то без возможности встраиваться в чужой event loop, в большинстве случаев решение останется за бортом. Как минимум в случае низколатентных приложений, где все потоки поделены по ядрам и отдать целый поток под один из интерфейсов это слегка не комильфо без возможности тонкого контроля, кто таки будет иметь приоритет при просыпании. Это частично можно настроить через api планировщика, но без близких к 99.9% гарантий.

Вот жизненный пример: хочу я дёргать из торгового бота rest api для смс рассылки и получать стейт от сервиса рассылки(например отчёт о прочтении). У меня есть 4 ядра(на самом деле 3 т.к. системе тоже надо где то крутится) и уже 5 потоков. Но один поток таки спит 99% времени, так же как будет спать и ваше решение. Я не в жизнь его не возьму, пока не смогу диспетчеризация сообщения вместе с потоком, который тоже соня, например по тайм ауту. Можно, конечно просто повесить эти два потока на одно ядро, но ощущения не те, видимо проще приделать для такой телеметрии велосипед.

Хотя, возможно я слишком далеко от web'а и где-то критично не прав.

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

Посыл не совсем такой. Наша идея в том, чтобы не приходилось думать, как прикрутить HTTP-вход в приложение. Т.е. взял черный ящик, запустил и «Опа!», а оно само работает.

В случае же когда есть какой-то чужой event-loop (и этот event-loop не Asio), то встроить HTTP-вход — это задача уже совсем другого уровня сложности. Здесь пользователю думать нужно. И это (может быть пока) не наша целевая аудитория.

Вот, скажем, в libcurl есть возможность крутить curl_multi на чужом event-loop-е. Но там все насколько неочевидно поначалу, что совсем невесело может стать от такой перспективы. Простотой интеграции там не пахнет.

ЗЫ. Помнится в начале работ над RESTinio мы рассматривали возможность сделать какую-то абстракцию над event-loop-ом. Чтобы RESTinio можно было запускать как поверх Asio, так и поверх libuv, так и поверх еще чего-нибудь. Но это оказалось слишком сложно и трудоемко. Мы не стали в такую историю ввязываться.

eao197 ★★★★★ ()
Ответ на: комментарий от pon4ik

чужой event loop

Тогда, имхо, его прикрутят в лучшем случае на старте или как ни странно к нечувствительному к производительности решению. А если необходимость взаимодействия с web появится в середине жизненного цикла продукта, то без возможности встраиваться в чужой event loop, в большинстве случаев решение останется за бортом. Как минимум в случае низколатентных приложений, где все потоки поделены по ядрам и отдать целый поток под один из интерфейсов это слегка не комильфо без возможности тонкого контроля, кто таки будет иметь приоритет при просыпании. Это частично можно настроить через api планировщика, но без близких к 99.9% гарантий.

Так для ясности, RESTinio повесить на asio::io_context, который пользователь создаст когда и где ему надо и будет дергать tuj rfr tve dplevftncz через одну из функций run*()(https://chriskohlhoff.github.io/networking-ts-doc/doc/networking_ts/reference...), т.е. это и получается чужой event loop. Чего, как я понял, тот же CROW не умеет.

kola ()
Ответ на: комментарий от eao197

Ага, понятно.

Ну, это моё имхо чего было бы интересно в таком, да и в любом решении которое делает что-то окромя i/o.

Просто, если отказаться/сделать опциональной идею асинхронности, то интеграция в чужой even loop может выглядеть как две функции:

void feed(client_id, data); // накормить данными из-вне
data poll(client_id); // получить данные или признак конца соединения

Тогда клиент в одном потоке с таймером каким нибудь сможет сделать оптимизации вида:

* проверили когда следующий deadline

* если не раньше чем через rest_clients_count * max_rest_io_cost => покормить и опросить rest приложение

* иначе спать до deadline

pon4ik ★★★★★ ()
Ответ на: чужой event loop от kola

Без относительно crow(тут я просто сравнение хотел увидеть, за что и спасибо).

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

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

Просто, если отказаться/сделать опциональной идею асинхронности

Готовых встраиваемых HTTP-серверов, в которых нет асинхронности, полно (по крайней мере «полно» относительно мира С и C++, в котором не так уж много чего есть). Тот же Crow, тот же POCO, Pistache, чисто-сишный civetweb.

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

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

В том то и дело, что как минимум poco(по крайней мере в той версии когда я её последний раз тыкал) точно нельзя покормить тем, что пришло с сокета здесь и сейчас, а продолжить чуть позже. Там везде происходит блокировка на i/o и сокет читается, пока таки не придёт весь ответ/не уйдёт весь запрос.

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

А у нас это из коробки :) Если из сокета пришла всего часть HTTP-запроса или ушла только часть HTTP-ответа, то RESTinio перейдет к обработке параллельных запросов или уснет на ожидании готовности I/O операций. Что позволяет вам на одной рабочей нити хоть 10k параллельных запросов обслуживать.

Другое дело, что в качестве event-loop-а у нас жесткая привязка к Asio (можно использовать standalone Asio или же из Boost-1.66). Других типов event-loop-ов (из libev, ACE, libuv или чего-то самодельного) мы не поддерживаем из-за слишком большой трудоемкости этой задачи.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Это прекрасно, с точки зрения решения в вакууме, так и надо делать, но я то хотел бы прямо в рамках текущего потока, пока идёт его квант, предпринять что-то поумнее чем сон :)

Повторюсь, это частично решается rt планировщиком, приоритетами и афинити, но без особой точности.

pon4ik ★★★★★ ()
Ответ на: комментарий от eao197

Других типов event-loop-ов (из libev, ACE, libuv или чего-то самодельного) мы не поддерживаем из-за слишком большой трудоемкости этой задачи.

Добавлю, что ASIO практически достоверно войдет в стандарт в виде networking, а значит в RESTinio может стать на одну зависимость меньше, что хорошо.

kola ()
Ответ на: комментарий от pon4ik

Это прекрасно, с точки зрения решения в вакууме, так и надо делать, но я то хотел бы прямо в рамках текущего потока, пока идёт его квант, предпринять что-то поумнее чем сон

Если вы в рамках текущего потока сможете управлять io_context, то все в ваших руках.

eao197 ★★★★★ ()
Ответ на: комментарий от kola

Согласен как только возможность покормить и заполать i/o решение будет стандартизирована и доступна из коробки такие решения станет делать в разы проще.

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

В зависимостях там http-parser — это один .c-ный файл, который нужно скомпилировать.

И Asio. В случае standalone Asio — это полностью header-only.

А вот в случае Boost-1.66 получается, что Boost.Asio — это header-only, а вот Boost.System — нет.

eao197 ★★★★★ ()

RESTinio-0.4.3 с компрессией

RESTinio обновлен до версии 0.4.4.

Что изменилось:

  • Добавлена концепция преобразователя данных, которая реализована для компрессии/декомпрессии данных с помощью библиотеки zlib. Подробне смотри: Compression (defalate, gzip).
    router->http_get(R"(/port/:id/latest)",
      [](auto req, auto params) {
        auto resp = req->create_response();
        resp.append_header_date_field()
            .append_header(restinio::http_field::content_type, "application/json");
        restinio::transforms::zlib::gzip_body_appender(resp)
            .append(load_port_data(params["id"]))
            .complete();
        return resp.done();
      });
    
    router->http_post(R"(/port/:id/data)",
      [](auto req, auto params) {
        return restinio::transforms::zlib::handle_body(*req, [&](auto uncompressed_body) {
            store_port_data(params["id"], upcompressed_body);
            return req->create_response()
                ...
                .done();
          });
      });
    
  • Добавлена функция value_or(), с помощью которой можно получить значение параметра из key-value контейнера RESTinio с подстановкой значения по-умолчанию. Подробне смотри: Get values from RESTinio key-value containers.

    Ранее приходилось писать так:

    const auto count = qp.has( "count" ) ? restinio::cast_to<unsigned>( qp[ "count" ] ) : 100u;
    
    Сейчас можно писать так:
    const auto count = restinio::value_or(qp, "count", 100u ) );
    

  • Переход на библиотеку https://github.com/martinmoene/string-view-lite для string_view (которая использует string_view из STL если это возможно).
  • Добавлена сторонняя библиотека https://github.com/martinmoene/optional-lite для optional (которая использует optional из STL если это возможно).

Библиотека по-прежнему живет на bitbucket-е (https://bitbucket.org/sobjectizerteam/restinio-0.4) c зеркалом на github-е (https://github.com/Stiffstream/restinio).

Документация доступна у нас на сайте (https://stiffstream.com/en/docs/restinio/0.4/).

Распространяется под BSD-3-CLAUSE лицензией.

kola ()

RESTinio 0.4.5.1 с портом под vcpkg

Что нового:

  • Набор cmake-файлов для сборки и использования RESTinio улучшены в соответствии с рекомендациями по modern cmake. Подробне смотри: CMake.
  • RESTinio доступен через vcpkg. Теперь вы можете использовать RESTinio с помощью команды вида
    $ vcpkg install restinio
    
  • Часть функций API переработана так, чтобы получать аргументы в виде string_view вместо ссылки на std::string, что должно уменьшить количество обращений к динамической памяти при работе с RESTinio.
  • Добавлена функция ``restinio::opt_value()``. Подробнее смотри: Get values from RESTinio key-value containers.

    Ранее приходилось писать вот так:

    const auto count = qp.has( "count" ) ?
      restinio::optional_t<unsigned>( restinio::cast_to<unsigned>( qp[ "count" ] ) ) :
      restinio::optional_t<unsigned>{};
    

    Теперь можно писать так:

    const auto count = opt_value< unsigned >( qp, "count" );
    

Библиотека живет на bitbucket-е (https://bitbucket.org/sobjectizerteam/restinio-0.4). Есть зеркало на github-е (https://github.com/Stiffstream/restinio).

Документация доступна у нас на сайте (https://stiffstream.com/en/docs/restinio/0.4/).

Распространяется под BSD-3-CLAUSE лицензией.

kola ()

Немного о том, как можно использовать RESTinio

Статья о небольшем проекте, в котором используется RESTinio: Shrimp: масштабируем и раздаем по HTTP картинки на современном C++ посредством ImageMagic++, SObjectizer и RESTinio.

TLDR: Shrimp небольшой сервис для раздачи по HTTP отмасштабированных изображений из заданной директории (github mirror).

kola ()

Re: Немного о том, как можно использовать RESTinio

ну сходу смотрел диагонально

auto do_404_response( restinio::request_handle_t req )
{
   auto resp = req->create_response( 404, "Not Found" );
   resp.append_header( restinio::http_field_t::server, "Shrimp draft server" );
   resp.append_header_date_field();

   if( req->header().should_keep_alive() )
      resp.connection_keep_alive();
   else
      resp.connection_close();

   return resp.done();
}
я привык что оператор доступа -> перегружен только в стандартных типах указателей unique_ptr,shared_ptr,weak_ptr..
зачем мне напрягаться что бы понять что объявленный тип restinio::request_handle_t req оказывается перегружен в операторе доступа ? для меня очевидней было бы считать что я должен и могу обращаться через точку
едем дальше зачем программисту использующему библиотеку напрягаться что бы понять где нужно или не нужно использовать std::move ?
   for( auto & hf : header_fields )
   {
      resp.append_header( std::move( hf.m_name ), std::move( hf.m_value ) );
   }

ну итд

anonymous ()

я привык что оператор доступа -> перегружен только в стандартных типах указателей unique_ptr,shared_ptr,weak_ptr..

Вам даже не придется менять привычки, ибо request_handle_t - это shared_ptr и есть:

using request_handle_t = std::shared_ptr< request_t >;

зачем мне напрягаться что бы

Чтобы прочитать документацию? Ну, например, чтобы понять, нужна вам эта конкретная библиотека или лучше взять что-то еще.

едем дальше зачем программисту использующему библиотеку напрягаться что бы понять где нужно или не нужно использовать std::move ?

Программисту не нужно напрягаться. Если ему пофиг на то, что в конкретном месте будет дополнительное копирование, он вполне может написать и так:

for( auto & hf : header_fields )
{
   resp.append_header( hf.m_name, hf.m_value );
}

Код с move был написан потому, что header_fields в данном контексте — это временный объект, содержимое которого будет уничтожена сразу после формирования набора заголовков. Соответственно, лучше смувить то, что уже аллоцировано, чем аллоцировать это заново.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

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

anonymous ()
Ответ на: комментарий от eao197

если вы переопределили алис на какой то там птр, то в его новом имени должно быть что то, что намекало на то что он подразумевает использования как указателя
у вас же кстати изобилие *_t которое рябит в глазах
т.е. всё типы и нет ни одного объекта
и это С++ объектно ориентированный язык

anonymous ()
Ответ на: комментарий от anonymous

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

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

у вас же кстати изобилие *_t которое рябит в глазах

Вкусовщина, у кого-то от PascalCase в VeryVeryLongAndSignificantNames рябит в глазах.

т.е. всё типы и нет ни одного объекта

А какие объекты вам нужны?

и это С++ объектно ориентированный язык

C++ мультипарадигменный язык.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

вы определили имя которое вам удобно но не удобно и не принято в обществе где вы это пытаетесь продвигать
если бы стандартная библиотека была вся в *_t я думаю многие бы на нее плевались, как хорошо что ее писали не вы
если вы не понимаете разницу между объектом и типом.. может вам больше не писать ничего на С++ ?
да я согласен что на С++ можно говнокодить как угодно, но если вы как то пиаритесь, то пиартесь что ли более красиво

anonymous ()
Ответ на: комментарий от anonymous

вы определили имя которое вам удобно но не удобно и не принято в обществе где вы это пытаетесь продвигать

В обществе, где мы «пытаемся это продвигать», кроме нотации из stdlib/boost-а широко используются и другие нотации. Посмотрите в ACE, POCO, folly, Crypto++, Catch и пр.

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

Если бы мы писали стандартную библиотеку, то мы бы следовали соглашениям стандартной библиотеки. Кроме того, обычного C++ника суффиксом _t нельзя напугать, т.к. он знаком с ним как по stdlib, так и по POSIX-у.

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

если вы не понимаете разницу между объектом и типом.. может вам больше не писать ничего на С++ ?

Вы вопрос прочитайте сперва. Он был не о разнице между объектом и типом, а о том, какие объекты в RESTinio вам нужны. Мы, например, считаем, что объектов в RESTinio достаточно. Если вам чего-то не хватает, то скажите, чего именно. Сможете?

eao197 ★★★★★ ()

RESTinio 0.4.7

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

Что нового:

  • Добавлена поддержка заголовочных полей из списка Provisional Message Header Field Names (см. https://www.iana.org/assignments/message-headers/message-headers.xml).

    Ранее приходилось писать вот так:

    resp.append_header( "Access-Control-Allow-Origin", "*" );
    
    Теперь можно писать так:
    resp.append_header( restinio::http_field_t::access_control_allow_origin, "*" );
    

  • Интерфейс http_header_field_t расширен getter-ами и setter-ами. Это привносит небольшую несовместимость с предыдущими версиями в случаях, когда имеются обращения к полям http_header_field_t напрямую, которые теперь скрытые (private) члены данных.
  • Добавлена еще одна перегрузка base_response_builder_t::append_header(), которая принимает аргумент типа http_header_field_t:
    // Global variable
    const http_header_field_t cached_server_hf{restinio::http_field_t::server, "My server"};
    
    // In some function.
    // Use cached value and avoid extra
    // restinio::http_field_t to string representation lookup.
    resp.append_header( cached_server_hf );
    
  • Добавлена функция restinio::make_date_field_value(), которая возвращает строку строку с датой и временем в формате нужном для заголовочных полей (например «Fri, 15 Jun 2018 13:58:18 GMT»).
  • В класс sendfile_t добавлена метаинформация о файле. Подробнеe смотри File meta.

    Теперь проще получить время последней модификации файла:

    auto sf = restinio::sendfile( file_path );
    auto modified_at = restinio::make_date_field_value( sf.meta().last_modified_at() );
    
    req->create_response()
      // ...
      .append_header(
        restinio::http_field::last_modified,
        std::move( modified_at ) )
      // ...
    

  • Добавлен класс http_status_line_t и другие вспомогательные классы, с помощью этого класса проще задавать статус для HTTP-ответа. Подробне смотри Status line. Это избавляет от необходимости задавать status_code и reason_phrase вручную для стандартных кодов ответа:
    req->create_response( restinio::status_not_found() ); // 404 Not Found
    req->create_response( restinio::status_bad_request() ); // 400 Bad Request
    req->create_response( restinio::status_not_implemented() ); // 501 Not Implemented
    

Помимо этого обновлены сторонние зависимости.

Библиотека живет на bitbucket-е (https://bitbucket.org/sobjectizerteam/restinio-0.4) c зеркалом на github-е (https://github.com/Stiffstream/restinio).

Документация доступна у нас на сайте (https://stiffstream.com/en/docs/restinio/0.4/).

Распространяется под BSD-3-CLAUSE лицензией.

kola ()

RESTinio 0.4.8

Очередная итерация в развитии RESTinio.

Что нового в этой версии:

  • Добавлены нотификаторы о статусе записи данных. Нужно определить был ли ответ отправлен и каков статус записи данных в сокет? Тогда используйте нотификаторы:
     
    int main()
    {
      restinio::run(
        restinio::on_this_thread<>()
          .port(8080)
          .address("localhost")
          .request_handler([](auto req) {
            return req->create_response()
                     .set_body("Hello, World!")
                     .done( [](const auto & ec ){
                       std::cout << "Sending response status: " << ec << std::endl;
                     });
          }));
    
      return 0;
    }
    
  • Добавлена поддержка datasizeable типов в качестве буфера. Теперь с определенными ограничениями в качестве буфера можно использовать любой тип.
    // Blob for image, wraps a Image magick Blob
    // that is already a smart pointer.
    struct datasizable_blob_t
    {
      // Datasizeable interface:
      const void * data() const noexcept { return m_blob.data(); }
      std::size_t size() const noexcept { return m_blob.length(); }
    
      //! Image data blob.
      Magick::Blob m_blob;
    };
    
    // Somewhere in handler:
    datasizable_blob_t body;
    image.write( &(body.m_blob) ); // Writes image binary data to blob.
    resp.set_body( body );
    
  • В логирование добавлено сообщение со статусом (status line) отправляемых ответов:
    [2018-08-20 21:46:58.482] TRACE: [connection:1] start response (#0): HTTP/1.1 200 OK
    
  • В построители ответов (response builders) добавлены перегрузки сеттеров с квалификатором rvalue.

Библиотека живет на bitbucket-е (https://bitbucket.org/sobjectizerteam/restinio-0.4) c зеркалом на github-е (https://github.com/Stiffstream/restinio).

Документация доступна у нас на сайте (https://stiffstream.com/en/docs/restinio/0.4/). А также появилась doxygen документация: https://stiffstream.com/en/docs/restinio/0.4-api/

Распространяется под BSD-3-CLAUSE лицензией.

kola ()

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

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

eao197 ★★★★★ ()