Stiffstream / restinio

Cross-platform, efficient, customizable, and robust asynchronous HTTP(S)/WebSocket server C++ library with the right balance between performance and ease of use
Other
1.15k stars 93 forks source link

Chain express routers #65

Open prince-chrismc opened 4 years ago

prince-chrismc commented 4 years ago

Feature request.

Building a server that has to support multiple restful APIs it's nice to keep things modular. It would be nice to be able to run a server built with one request handler that dispatches to the other routers to handle individual APIs. This would allow for better separation, as apposed to having a "god class" with knowledge of everything.

Attempt at an example:

namespace rr = restinio::router;
using router_t = rr::express_router_t<>;

auto getFooApiRouter(){
   auto router = std::make_unique< router_t >();
   router->http_get(  "/:version" , &fooVersion );
   router->http_get(  "/:version/one" , &fooOne );
   router->http_put(  "/:version/one" , &fooOne );
   //... etc
   return router;
}

auto getBarApiRouter(){
   auto router = std::make_unique< router_t >();
   router->http_get(  "/:version" , &barVersion );
   router->http_get(  "/:version/thing" , &barThing );
   router->http_put(  "/:version/buzz" , &barBuzz );
   //... etc
   return router;
}

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

router->http_get( "/", &rootPage );
router->add_routes(  "/foo" , getFooApiRouter() );
router->add_routes(  "/bar" , getBarApiRouter() );

restinio::run(
    restinio::on_this_thread< traits_t >()
    .address( "localhost" )
    .request_handler( router ) );

I wanted to see if it was possible at all, tried this prince-chrismc/restinio@6a9edbd7828ff75d61c7b52c2d71ca62d835222f and prince-chrismc/restinio@e9b74ec8aafef9cfad5fd865d15373bcf7900a0b, which seems like a sufficient solution to preferred concept. The individual API routers, would need the full path but I could live with that.

eao197 commented 4 years ago

Hi!

I think that this particular task can be solved without modifying RESTinio's code. The one solution is:

void defineFooApiRouter(router_t & router, string_view & root) {
   router.http_get( fmt::format("{}/:version", root) , &fooVersion );
   router.http_get( fmt::format("{}/:version/one", root) , &fooOne );
   router.http_put( fmt::format("{}/:version/one", root) , &fooOne );
   //... etc
}

void defineBarApiRouter(router_t & router, string_view & root) {
   router.http_get( fmt::format("{}/:version", root), &barVersion );
   router.http_get( fmt::format("{}/:version/thing", root), &barThing );
   router.http_put( fmt::format("{}/:version/buzz", root), &barBuzz );
   //... etc
   return router;
}

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

router->http_get( "/", &rootPage );
defineFooApiRouter(*router, "/foo");
defineBarApiRouter(*router, "/bar");

restinio::run(
    restinio::on_this_thread< traits_t >()
    .address( "localhost" )
    .request_handler( router ) );

However, I think there are several flaws in the current routing mechanism and I have some thoughts about fixing them.

One of the flaws is the repetition of several actions during defining and handling paths. A very simple example of what can be seen here. I have to repeat expressions like /:value(\d+):multiplier([MmKkBb]?) and calls to extract_chunk_size(params).

It would be great to have some way to avoid that and to receive more type checking. Unfortunately, I don't know yet how it can look like. Maybe something like this:

struct chunk_size_info {
  size_t value_;
  optional<char> multiplier_;

  size_t actual_value() const noexcept { ... }
};
struct chunk_count_info {
  size_t count_;
};

auto chunk_size_fragment = restinio::router::make_path_fragment<chunk_size_info>(
  &chunk_size_info::value_, &chunk_size_info::multiplier);
auto chunk_count_fragment = restinio::router::make_path_fragment<chunk_count_info>(
  &chunk_count_info);
...
router->http_get(root / chunk_size_fragment, ...);
router->http_get(root / chunk_size_fragment / chunk_count_fragment,  ...);

It seems that if that approach will be implemented it can solve the your task too.

Anyway, thanks for the feature requests!

prince-chrismc commented 4 years ago

Thank you very much for the quick response. I love the syntax you are proposing root / path_fragment / endpoint would be very descriptive.

I have opted for using you're proposal to extend multiple APIs. The suggestion is greatly appreciated =)

eao197 commented 4 years ago

@lpc921

I think it's necessary to do something like that:

auto router = std::make_unique<router_t>();
... // Setup handlers for the router.
restinio::run(restinio::on_this_thread<traits_t>()
                    .address("localhost")
                    .request_handler(std::move(router)));