We continue to run into multi-storey C ++ templates in RESTinio: a type-safe alternative to express-js router


RESTinio , our small embedded HTTP server, continues to evolve . One of the distinguishing features of RESTinio is that in its implementation, multi-storey C ++ templates are actively used (as mentioned earlier: 1 , 2 ).


C++ RESTinio , , , RESTinio: , .


( ) , run-time. RESTinio, , . , .


easy_parser_router express-router-


express-router ?


ExpressJS RESTinio , . , , . , , - . , , .



express- . , , :


router->http_get("/api/v1/books/:id",
   [](const auto & req, auto params) {
      const auto book_id = restinio::cast_to<std::uint64_t>(params["Id"]);
      ...
   });

, , "id". "id" ( ), "Id" ( ).


β€” id. /api/v1/books/:id "id" , , , .


, :


router->http_get(R"(/api/v1/books/:id(\d+))",
   [](const auto & req, auto params) {
      const auto book_id = restinio::cast_to<std::uint64_t>(params["id"]);
      ...
   });

, .


, "id" , 64- .


, , , :


router->http_get(R"(/api/v1/books/:id(\d{1,10}))",
   [](const auto & req, auto params) {
      const auto book_id = restinio::cast_to<std::uint64_t>(params["id"]);
      ...
   });

, run-time. .., , .



, :


class api_v1_handler {
   ...
public:
   auto on_get_book(
         const restinio::request_handle_t & req,
         restinio::router::route_params_t params)
   {
      const auto book_id = restinio::cast_to<std::uint64_t>(params["id"]);
      ...
   }

   auto on_get_book_version(
         const restinio::request_handle_t & req,
         restinio::router::route_params_t params)
   {
      const auto book_id = restinio::cast_to<std::uint64_t>(params["id"]);
      const auto ver_id = restinio::cast_to<std::string>(params["version"]);
      ...
   }

   auto on_get_author_books(
         const restinio::request_handle_t & req,
         restinio::router::route_params_t params)
   {
      const auto author = restinio::cast_to<std::string>(params["author"]);
      ...
   }
   ...
};

- - , .


:


auto handler = std::make_shared<api_v1_handler>(...);
router->http_get(R"(/api/v1/books/:id(\d{1,10}))",
   [handler](const auto & req, auto params) {
      return handler->on_get_book_version(req, params);
   });
router->http_get(R"(/api/v1/books/:id(\d{1,10})/versions/:version)",
   [handler](const auto & req, auto params) {
      return handler->on_get_author_books(req, params);
   });
router->http_get(R"(/api/v1/:author)",
   [handler](const auto & req, auto params) {
      return handler->on_get_book(req, params);
   });

. , , . , .


, api_v1_handler. express- , , :


class api_v1_handler {
   ...
   auto on_get_book_version(
         const restinio::request_handle_t & req,
         std::uint64_t book_id,
         const std::string & ver_id) { ... }
   ...
};

auto handler = std::make_shared<api_v1_handler>(...);
router->http_get(R"(/api/v1/books/:id(\d{1,10})/versions/:version)",
   [handler](const auto & req, auto params) {
      return handler->on_get_book_version(req,
            restinio::cast_to<std::uint64_t>(params["id"]),
            restinio::cast_to<std::string>(params["version"]));
   });

. , . . , , .


easy_parser_router


0.6.6 RESTinio express- easy_parser_router , easy_parser_router .


"id":


namespace epr = restinio::router::easy_parser_router;
router->http_get(
   epr::path_to_params(
      "/api/v1/books/",
      epr::non_negative_decimal_number_p<std::uint64_t>()),
   [](const auto & req, std::uint64_t book_id) {
      ...
   });

, , HTTP GET , api/v1/books/ 64- , . :


  • β€” restinio::request_handle_t, ;
  • β€” 64- , .

, URL book_id , uint64_t, " " .


api_v1_handler . api_v1_handler:


class api_v1_handler {
   ...
public:
   using book_id_type = std::uint64_t;

   auto on_get_book(
         const restinio::request_handle_t & req,
         book_id_type book_id) { ... }

   auto on_get_book_version(
         const restinio::request_handle_t & req,
         book_id_type book_id,
         const std::string & ver_id) { ... }

   auto on_get_author_books(
         const restinio::request_handle_t & req,
         const std::string & author) { ... }
   ...
};

- . -.


:


namespace epr = restinio::router::easy_parser_router;

auto book_id_p = epr::non_negative_decimal_number_p<api_v1_handler::book_id_type>();
auto ver_id_p = epr::path_fragment_p();
auto author_p = epr::path_fragment_p();

auto handler = std::make_shared<api_v1_handler>(...);
router->http_get(
   epr::path_to_params("/api/v1/books/", book_id_p),
   [handler](const auto & req, auto book_id) {
      return handler->on_get_book(req, book_id);
   });
router->http_get(
   epr::path_to_params("/api/v1/books/", book_id_p, "/versions/", ver_id_p),
   [handler](const auto & req, auto book_id, const auto & ver_id) {
      return handler->on_get_book_version(req, book_id, ver_id);
   });
router->http_get(
   epr::path_to_params("/api/v1/", author_p),
   [handler](const auto & req, const auto & author) {
      return handler->on_get_author_books(req, author);
   });

on_get_book , on_get_author_books.


path_to_params. path_to_tuple


, easy_parser_router-, path_to_params, . variadic template , , .. producer-.


path_to_params -. , path URL, HTTP-.


URL path . , path_to_params -producer . , path_to_params producer, . producer-, . , . .. path_to_params producer-, , , :


router->http_get(epr::path_to_params("/"), [](const auto & req) {...});

.


, , path_to_params β€” URL HTTP- .


path_to_params β€” . .., path_to_params producer-, :


router->http_get(
   epr::path_to_params("/api/v1/books/", book_id_p, "/versions/", ver_id_p),
   [handler](const auto & req, auto book_id, const auto & ver_id) {
      return handler->on_get_book_version(req, book_id, ver_id);
   });

, producer-, . - : β€” restinio::request_handle_t, β€” producer-.


, - , path_to_params path_to_tuple. path_to_tuple path_to_params , path_to_tuple producer- . :


router->http_get(
   epr::path_to_tuple("/api/v1/books/", book_id_p, "/versions/", ver_id_p),
   [handler](const auto & req, std::tuple<std::uint64_t, std::string> params) {
      return handler->on_get_book_version(req, std::get<0>(params), std::get<1>(params));
   });

easy_parser easy_parser_router?


, RESTinio HTTP-, RESTinio easy_parser. Parsing Expression Grammar (PEG), PEG- C++ DSL. , , :


limit = "limit" [SPACE] ":" [SPACE] NUMBER SPACE "bytes"

"limit:4096 bytes" "limit: 4096 bytes", easy_parser- :


using namespace restinio::easy_parser;
auto parser = produce<unsigned int>(
   exact("limit"),
   maybe(space()),
   symbol(':'),
   maybe(space()),
   non_negative_decimal_number_p<unsigned int>(),
   space(),
   exact("bytes"));

, , , unsigned int.


easy_parser_router RESTinio-0.6.6 easy_parser-. easy_parser_router easy_parser- - URL, easy_parser-.


, easy_parser, , easy_parser_router- - long_output .


: GET- URL /, /<size> /<size>/<count>, <size> <count> . , URL /512k/1024 , 1024 512KiB . URL /1200/500 , 500 1200 .


express- URL :


std::size_t extract_chunk_size(const restinio::router::route_params_t & params) {
   const auto multiplier = [](const auto sv) noexcept -> std::size_t {
      if(sv.empty() || "B" == sv || "b" == sv) return 1u;
      else if("K" == sv || "k" == sv) return 1024u;
      else return 1024u*1024u;
   };

   return restinio::cast_to<std::size_t>(params["value"]) *
         multiplier(params["multiplier"]);
}
...
auto router = std::make_unique<router_t>();

router->http_get("/", [&ctx](auto req, auto) {...});

router->http_get(
         R"(/:value(\d+):multiplier([MmKkBb]?))",
         [&ctx](auto req, auto params) {

      const auto chunk_size = extract_chunk_size(params);
      ...
   });

router->http_get(
         R"(/:value(\d+):multiplier([MmKkBb]?)/:count(\d+))",
         [&ctx](auto req, auto params) {

      const auto chunk_size = extract_chunk_size(params);
      const auto count = restinio::cast_to<std::size_t>(params["count"]);
      ...
   });

easy_parser_router- :


using namespace restinio::router::easy_parser_router;

auto router = std::make_unique<router_t>();

struct distribution_params
{
   std::size_t chunk_size_{100u*1024u};
   std::size_t count_{10000u};
};
struct chunk_size { std::uint32_t c_{1u}, m_{1u}; };

router->http_get(
   path_to_params(
      produce<distribution_params>(
         exact("/"),
         maybe(
            produce<chunk_size>(
               non_negative_decimal_number_p<std::uint32_t>()
                  >> &chunk_size::c_,
               maybe(
                  produce<std::uint32_t>(
                     alternatives(
                        caseless_symbol_p('b') >> just_result(1u),
                        caseless_symbol_p('k') >> just_result(1024u),
                        caseless_symbol_p('m') >> just_result(1024u * 1024u)
                     )
                  ) >> &chunk_size::m_
               )
            ) >> convert(
                  [](auto cs) { return std::size_t{cs.c_} * cs.m_; })
               >> &distribution_params::chunk_size_,
            maybe(
               exact("/"),
               non_negative_decimal_number_p<std::size_t>()
                  >> &distribution_params::count_
            )
         )
      )
   ),
   [&ctx](const auto & req, const auto & params ) { ... });

.. URL URL. URL PEG-:


path = "/" [NUMBER [((B|b) | (K|k) | (M|m))] ["/" NUMBER]]

C++ , PEG- distribution_params, : . C++ , b, k m.


easy_parser , , , . ...


-, . , ( , ). , PEG- " , ".


-, easy_parser, easy_parser_router- . β€” , . , , don't shoot the pianist… , - , DSL C++14, , .


, produce, maybe, alternatives, just_result, convert . . . - , . easy_parser- , . , .


easy_parser- easy_parser_router-


easy_parser easy_parser_router, " C++ ". , - C++, , , , .


result_value_wrapper result_wrapper_for


easy_parser- , PEG- - . DSL easy_parser- β€” produce:


template<typename Target_Type, typename... Clauses>
auto produce(Clauses &&... clauses);

Target_Type , . Clauses .


, - :


// :
// kv = key "->" value
// key = NUMBER
// value = NUMBER
struct KV{ int key; int value; };
produce<KV>(
   decimal_number_p<int>() >> &KV::key,
   exact("->"),
   decimal_number_p<int>() >> &KV::value);

:


expected<KV, parsing_error_t>
try_produce_KV_(impl::input_t & from) {
   KV result_value;
   {
      impl::decimal_number_producer_t<int> p;
      const auto r = p.try_produce(from);
      if(!r) return make_unexpected(r.error());
      impl::field_setter_consumer_t<&KV::key> consumer;
      consumer.consume(result_value, *r); // result_value.key = *r;
   }
   {
      impl::exact_clause_t c{"->"};
      const auto r = c.try_process(from, result_value);
      if(r) return make_unexpected(*r);
   }
   {
      impl::decimal_number_producer_t<int> p;
      const auto r = p.try_produce(from);
      if(!r) return make_unexpected(r.error());
      impl::field_setter_consumer_t<&KV::value> consumer;
      consumer.consume(result_value, *r); // result_value.value = *r;
   }
   return result_value;
}

.. , produce<KV>(clauses...), KV, clauses. clauses , produce<KV>.


, produce<T> T β€” - int long, - . T . , - :


// :
// keys_values = (kv [","])+
// kv = key "->" value
// key = NUMBER
// value = NUMBER
struct KV{ int key; int value; };
produce<std::vector<KV>>(
   repeat(1, N,
      produce<KV>(
         decimal_number_p<int>() >> &KV::key,
         exact("->"),
         decimal_number_p<int>() >> &KV::value
      ) >> to_container(),
      maybe(exact(","))
   ));

.. KV, std::vector<KV>.


DSL- easy_parser_router- .


() , , , , " ", " " . , , , std::vector std::string.


std::array, , . - :


produce<std::array<char, 8>>(
   repeat(8, 8, hexdigit_p() >> to_container()));

: , std::array<char, 8>?


produce<std::array<char, 8>> std::array<char, 8>, .


, std::array , - . std::vector std::string .


result_value_wrapper easy_parser , produce<T>(clauses...) , produce clauses. .. produce<T>(...) - :


expected_t<T, parsing_error_t>
try_produce_T_(impl::input_t & from) {
   typename result_value_wrapper<T>::wrapped_type result_value;
   ...
   return result_value_wrapper<T>::unwrap_value(result_value);
}

easy_parser- result_value_wrapper . , std::vector:


template< typename T, typename... Args >
struct result_value_wrapper< std::vector< T, Args... > >
{
   using result_type = std::vector< T, Args... >;
   using value_type = typename result_type::value_type;
   using wrapped_type = result_type;

   static void
   as_result( wrapped_type & to, result_type && what )
   {
      to = std::move(what);
   }

   static void
   to_container( wrapped_type & to, value_type && what )
   {
      to.push_back( std::move(what) );
   }

   RESTINIO_NODISCARD
   static result_type &&
   unwrap_value( wrapped_type & v )
   {
      return std::move(v);
   }
};

, , std::array:


namespace impl
{

template< typename T, std::size_t S >
struct std_array_wrapper
{
   std::array< T, S > m_array;
   std::size_t m_index{ 0u };
};

} /* namespace impl */

template< typename T, std::size_t S >
struct result_value_wrapper< std::array< T, S > >
{
   using result_type = std::array< T, S >;
   using value_type = typename result_type::value_type;
   using wrapped_type = impl::std_array_wrapper< T, S >;

   static void
   as_result( wrapped_type & to, result_type && what )
   {
      to.m_array = std::move(what);
      to.m_index = 0u;
   }

   static void
   to_container( wrapped_type & to, value_type && what )
   {
      if( to.m_index >= S ) throw exception_t(...);

      to.m_array[ to.m_index ] = std::move(what);
      ++to.m_index;
   }

   RESTINIO_NODISCARD
   static result_type &&
   unwrap_value( wrapped_type & v )
   {
      return std::move(v.m_array);
   }
};

produce<std::vector<T>>(...) std::vector<T>, produce<std::array<T, 10>>(...) impl::std_array_wrapper<T, 10>.


, result_value_wrapper . produce:


expected<KV, parsing_error_t>
try_produce_KV_(impl::input_t & from) {
   typename result_value_wrapper<KV>::wrapped_type result_value;
   {
      impl::decimal_number_producer_t<int> p;
      const auto r = p.try_produce(from);
      if(!r) return make_unexpected(r.error());
      impl::field_setter_consumer_t<&KV::key> consumer;
      consumer.consume(result_value, *r); // (1)
   }
   ...
}

(1) consume result_value_wrapper<KV>::wrapped_type. consume , . KV, - KV. consume .


"" result_wrapper_for, wrapped_type result_value_wrapper. consume :


template< typename Target_Type, typename Value >
void
consume( Target_Type & dest, Value && src ) const
{
   using W = typename result_wrapper_for<Target_Type>::type;
   W::as_result( dest, std::forward<Value>(src) );
}

, - result_value_wrapper wrapped_type, result_type, result_wrapper_for:


template< typename T, std::size_t S >
struct result_wrapper_for< impl::std_array_wrapper<T, S> >
{
   using type = result_value_wrapper< std::array< T, S > >;
};

, consume<Target_Type, Value>() Target_Type impl::std_array_wrapper<T, S> ( wrapped_type std::array), consume , result_value_wrapper<std::array<T, S>> Target_Type.


transformer_proxy


easy_parser , :


  • producer - ;
  • transformer producer- . transformer- , , producer() >> transformer_one() >> transformer_two() >> transformer_three(). .. transformer >>, producer, transformer. transformer, consumer;
  • consumer "" producer- . , -. consumer >> producer, transformer, . .. consumer >>;
  • clause. producer() >> ... >> consumer(), clause, , .

. , , producer- transformer-, result_type.


easy_parser- ( to_lower ) result_type , , .


convert ( map): (), . convert :


produce<chunk_size>(
   non_negative_decimal_number_p<std::uint32_t>()
      >> &chunk_size::c_,
   maybe(
      produce<std::uint32_t>(
         alternatives(
            caseless_symbol_p('b') >> just_result(1u),
            caseless_symbol_p('k') >> just_result(1024u),
            caseless_symbol_p('m') >> just_result(1024u * 1024u)
         )
      ) >> &chunk_size::m_
   )
) >> convert( // (1)
      [](auto cs) { return std::size_t{cs.c_} * cs.m_; })
   >> &distribution_params::chunk_size_,

(1) convert , chunk_size, std::size_t.


, . .. .


.. convert :


template<typename Callable>
SomeTransformerType convert(Callable && f) {...}

transformer-.


, SomeTransformerType result_type. convert, . convert producer-.


easy_parser : transformer_proxy.


Transformer_proxy transfomer-, , transformer- , transformer_proxy producer-.


transformer_proxy operator>> :


template<
   typename P,
   typename T,
   typename S = std::enable_if_t<
         is_producer_v<P> & is_transformer_proxy_v<T>,
         void > >
RESTINIO_NODISCARD
auto
operator>>(P producer, T transformer_proxy )
{
   auto real_transformer = transformer_proxy.template make_transformer< 
         typename P::result_type >();

   using transformator_type = std::decay_t< decltype(real_transformer) >;

   using producer_type = transformed_value_producer_t< P, transformator_type >;

   return producer_type{ std::move(producer), std::move(real_transformer) };
};

convert, .. transformer_proxy, - . transformer_proxy operator>>, - make_transformer transformer- result_type :


template< typename Converter >
class convert_transformer_proxy_t : public transformer_proxy_tag
{
   Converter m_converter;

public :
   ...
   template< typename Input_Type >
   RESTINIO_NODISCARD
   auto
   make_transformer() const &
   {
      using output_type = std::decay_t<
            decltype(m_converter(std::declval<Input_Type&&>())) >;

      return convert_transformer_t< output_type, Converter >{ m_converter };
   }
   ...
};

output_type make_transformer β€” result_type convert_transformer_t.


path_to_params/path_to_tuple


easy_parser_router easy_parser, easy_parser_router DSL path_to_params path_to_tuple. , :


router->http_get(
   path_to_params("/"),
   [](const auto & req) {...});

router->http_get(
   path_to_params("/api/v1/books/", non_negative_decimal_number_p<int>()),
   [](const auto & req, int book_id) {...});

, - :


router->http_get(
   route_to_params(produce<std::tuple<>>(
      exact_p("/") >> just_result(std::tuple<>{}))),
   [](const auto & req) {});

router->http_get(
   route_to_params(produce<std::tuple<int>>(
      exact("/api/v1/books/"),
      non_negative_decimal_number_p<int>() >> to_tuple<0>())),
  [](const auto & req, int book_id) {...});

, - DSL path_to_params (path_to_tuple) :


  • std::tuple, URL . result_type producer-, path_to_params (path_to_tuple);
  • clauses URL.

, :


path_to_params("/")

std::tuple<>. clauses - type_list<exact_fragment_clause_t>.


:


path_to_params(
   "/api/v1/books/",
   non_negative_decimal_number_p<int>(),
   "/versions/",
   path_fragment_p())

std::tuple<int, std::string>, clauses - : type_list<exact_fragment_clause_t, tuple_item_consumer_t<0, non_negative_decimal_number_producer_t<int>>, exact_fragment_clause_t, tuple_item_consumer_t<1, path_fragment_producer_t>>.


easy_parser_router ( ):


template< typename... Args >
struct dsl_processor
{
   static_assert( 0u != sizeof...(Args), "Args can't be an empty list" );

   using arg_types = meta::transform_t<
         dsl_details::special_decay, meta::type_list<Args...> >;

   using result_tuple = dsl_details::detect_result_tuple_t< arg_types >;

   using clauses_tuple = dsl_details::make_clauses_types_t< arg_types >;
};

, : arg_types result_tuple.


arg_types β€” , , const/volatile . , Args dsl_processor const T&, arg_types T.


arg_types - transform, . ( dsl_details::special_decay) . , .


meta::transform_t , C++14 ( Boost-).


result_tuple .


, result_tuple path_to_params (path_to_tuple) , producer-, result_type. :


template< typename Args_Type_List >
struct detect_result_tuple
{
   using type = meta::rename_t<
         typename result_tuple_detector<
               Args_Type_List,
               meta::type_list<> >::type,
         std::tuple >;
};

template< typename Args_Type_List >
using detect_result_tuple_t = typename detect_result_tuple<Args_Type_List>::type;

detect_result_tuple result_tuple_detector, . result_tuple_detector type_list<T...>, std::tuple<T...>. detect_result_tuple type_list<T...> std::tuple<T...> rename.


, result_tuple_detector. :


template< typename From, typename To >
struct result_tuple_detector;

template<
   template<class...> class From,
   typename... Sources,
   template<class...> class To,
   typename... Results >
struct result_tuple_detector< From<Sources...>, To<Results...> >
{
   using type = typename result_tuple_detector<
         meta::tail_of_t< Sources... >,
         typename add_type_if_necessary<
               meta::head_of_t< Sources... >,
               To< Results... > >::type
      >::type;
};

template<
   template<class...> class From,
   template<class...> class To,
   typename... Results >
struct result_tuple_detector< From<>, To<Results...> >
{
   using type = To<Results...>;
};

, C++ ", ..." , result_tuple_detector. , , :


template<
   template<class...> class From,
   template<class...> class To,
   typename... Results >
struct result_tuple_detector< From<>, To<Results...> >
{
   using type = To<Results...>;
};

. , . , β€” result_tuple_detector.


, , :


template<
   template<class...> class From,
   typename... Sources,
   template<class...> class To,
   typename... Results >
struct result_tuple_detector< From<Sources...>, To<Results...> >
{
   using type = typename result_tuple_detector<
         meta::tail_of_t< Sources... >,
         typename add_type_if_necessary<
               meta::head_of_t< Sources... >,
               To< Results... > >::type
      >::type;
};

: , "" , , add_type_if_necessary<H, R_List>. . add_type_if_necessary<H, R_List> (R_List), H producer-. , R_List H::result_type, H producer-.


add_type_if_necessary :


template< typename H, typename R, bool Is_Producer >
struct add_type_if_necessary_impl;

template<
   typename H,
   template<class...> class To,
   typename... Results >
struct add_type_if_necessary_impl< H, To<Results...>, false >
{
   using type = To<Results...>;
};

template<
   typename H,
   template<class...> class To,
   typename... Results >
struct add_type_if_necessary_impl< H, To<Results...>, true >
{
   using type = To<Results..., typename H::result_type>;
};

// Adds type H to type list R if H is a producer.
template< typename H, typename R >
struct add_type_if_necessary
   : add_type_if_necessary_impl< H, R, ep::impl::is_producer_v<H> >
{};

is_producer_v β€” - easy_parser, , producer- .



easy_parser_router β€” ...


… . , , C++ compile-time, .


, HTTP- C++ β€” - , . - RESTinio.


, .. -. easy_parser β€” . , .


, .


, , . , . .



, easy_parser easy_parser_router RESTinio . , , .


, , C++. , .


, . . .


, , - . C++ - , .


- RESTinio easy_parser_router , .



, .


- , RESTinio, easy_parser / easy_parser_router, .


, RESTinio.


All Articles