From e60714c0b2e5ea4656916a5b658a8d5eada98a3a Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 1 Feb 2022 16:31:05 +0300 Subject: [PATCH 01/10] Separate middleware for handlers --- examples/example_middleware.cpp | 55 ++++++ include/crow.h | 1 + include/crow/app.h | 3 +- include/crow/http_connection.h | 151 +-------------- include/crow/http_request.h | 1 + include/crow/middleware.h | 308 ++++++++++++++++++++++++++++++ include/crow/middleware_context.h | 15 +- include/crow/routing.h | 128 ++++--------- include/crow/utility.h | 31 +++ tests/unittest.cpp | 55 +++++- 10 files changed, 494 insertions(+), 254 deletions(-) create mode 100644 examples/example_middleware.cpp create mode 100644 include/crow/middleware.h diff --git a/examples/example_middleware.cpp b/examples/example_middleware.cpp new file mode 100644 index 000000000..f04264e1d --- /dev/null +++ b/examples/example_middleware.cpp @@ -0,0 +1,55 @@ +#include "crow.h" + +struct RequestLogger +{ + struct context + {}; + + void before_handle(crow::request& req, crow::response& /*res*/, context& /*ctx*/) + { + CROW_LOG_INFO << "Request to:" + req.url; + } + + void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/) + {} +}; + +// Per handler middleware has to extend ILocalMiddleware +// It is called only if enabled +struct SecretContentGuard : crow::ILocalMiddleware +{ + struct context + {}; + + void before_handle(crow::request& /*req*/, crow::response& res, context& /*ctx*/) + { + res.write("SECRET!"); + res.code = 403; + res.end(); + } + + void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/) + {} +}; + +int main() +{ + // ALL middleware (including per handler) is listed + crow::App app; + + CROW_ROUTE(app, "/") + ([]() { + return "Hello, world!"; + }); + + CROW_ROUTE(app, "/secret") + // Enable SecretContentGuard for this handler + .middlewares() + ([]() { + return ""; + }); + + app.port(18080).run(); + + return 0; +} diff --git a/include/crow.h b/include/crow.h index 1687f9647..25c136ad6 100644 --- a/include/crow.h +++ b/include/crow.h @@ -17,6 +17,7 @@ #include "crow/http_response.h" #include "crow/multipart.h" #include "crow/routing.h" +#include "crow/middleware.h" #include "crow/middleware_context.h" #include "crow/compression.h" #include "crow/http_connection.h" diff --git a/include/crow/app.h b/include/crow/app.h index ec1b27e93..f88a3bdc9 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -68,7 +68,7 @@ namespace crow } /// Process the request and generate a response for it - void handle(const request& req, response& res) + void handle(request& req, response& res) { router_.handle(req, res); } @@ -394,6 +394,7 @@ namespace crow // middleware using context_t = detail::context; + using mw_container_t = std::tuple; template typename T::context& get_context(const request& req) { diff --git a/include/crow/http_connection.h b/include/crow/http_connection.h index d370d3fa0..22a0f879f 100644 --- a/include/crow/http_connection.h +++ b/include/crow/http_connection.h @@ -15,6 +15,7 @@ #include "crow/settings.h" #include "crow/task_timer.h" #include "crow/middleware_context.h" +#include "crow/middleware.h" #include "crow/socket_adaptors.h" #include "crow/compression.h" @@ -23,150 +24,6 @@ namespace crow using namespace boost; using tcp = asio::ip::tcp; - namespace detail - { - template - struct check_before_handle_arity_3_const - { - template - struct get - {}; - }; - - template - struct check_before_handle_arity_3 - { - template - struct get - {}; - }; - - template - struct check_after_handle_arity_3_const - { - template - struct get - {}; - }; - - template - struct check_after_handle_arity_3 - { - template - struct get - {}; - }; - - template - struct is_before_handle_arity_3_impl - { - template - static std::true_type f(typename check_before_handle_arity_3_const::template get*); - - template - static std::true_type f(typename check_before_handle_arity_3::template get*); - - template - static std::false_type f(...); - - public: - static const bool value = decltype(f(nullptr))::value; - }; - - template - struct is_after_handle_arity_3_impl - { - template - static std::true_type f(typename check_after_handle_arity_3_const::template get*); - - template - static std::true_type f(typename check_after_handle_arity_3::template get*); - - template - static std::false_type f(...); - - public: - static const bool value = decltype(f(nullptr))::value; - }; - - template - typename std::enable_if::value>::type - before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) - { - mw.before_handle(req, res, ctx.template get(), ctx); - } - - template - typename std::enable_if::value>::type - before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) - { - mw.before_handle(req, res, ctx.template get()); - } - - template - typename std::enable_if::value>::type - after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) - { - mw.after_handle(req, res, ctx.template get(), ctx); - } - - template - typename std::enable_if::value>::type - after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) - { - mw.after_handle(req, res, ctx.template get()); - } - - template - bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx) - { - using parent_context_t = typename Context::template partial; - before_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); - - if (res.is_completed()) - { - after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); - return true; - } - - if (middleware_call_helper(middlewares, req, res, ctx)) - { - after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); - return true; - } - - return false; - } - - template - bool middleware_call_helper(Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/) - { - return false; - } - - template - typename std::enable_if<(N < 0)>::type - after_handlers_call_helper(Container& /*middlewares*/, Context& /*context*/, request& /*req*/, response& /*res*/) - { - } - - template - typename std::enable_if<(N == 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res) - { - using parent_context_t = typename Context::template partial; - using CurrentMW = typename std::tuple_element::type>::type; - after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); - } - - template - typename std::enable_if<(N > 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res) - { - using parent_context_t = typename Context::template partial; - using CurrentMW = typename std::tuple_element::type>::type; - after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); - after_handlers_call_helper(middlewares, ctx, req, res); - } - } // namespace detail #ifdef CROW_ENABLE_DEBUG static std::atomic connectionCount; @@ -316,8 +173,11 @@ namespace crow ctx_ = detail::context(); req.middleware_context = static_cast(&ctx_); + req.middleware_container = static_cast(middlewares_); req.io_service = &adaptor_.get_io_service(); - detail::middleware_call_helper<0, decltype(ctx_), decltype(*middlewares_), Middlewares...>(*middlewares_, req, res, ctx_); + + detail::middleware_call_helper(*middlewares_, req, res, ctx_); if (!res.completed_) { @@ -351,6 +211,7 @@ namespace crow // call all after_handler of middlewares detail::after_handlers_call_helper< + detail::middleware_call_criteria_only_global, (static_cast(sizeof...(Middlewares)) - 1), decltype(ctx_), decltype(*middlewares_)>(*middlewares_, ctx_, req_, res); diff --git a/include/crow/http_request.h b/include/crow/http_request.h index 809f5edfe..c27778005 100644 --- a/include/crow/http_request.h +++ b/include/crow/http_request.h @@ -34,6 +34,7 @@ namespace crow std::string remote_ip_address; ///< The IP address from which the request was sent. void* middleware_context{}; + void* middleware_container{}; boost::asio::io_service* io_service{}; /// Construct an empty request. (sets the method to `GET`) diff --git a/include/crow/middleware.h b/include/crow/middleware.h new file mode 100644 index 000000000..d527845f7 --- /dev/null +++ b/include/crow/middleware.h @@ -0,0 +1,308 @@ +#pragma once + +#include "crow/http_request.h" +#include "crow/http_response.h" +#include "crow/utility.h" + +#include +#include +#include +#include + +namespace crow { + + /// Local middleware should extend ILocalMiddleware + struct ILocalMiddleware { + using call_global = std::false_type; + }; + + namespace detail + { + template + struct check_before_handle_arity_3_const + { + template + struct get + {}; + }; + + template + struct check_before_handle_arity_3 + { + template + struct get + {}; + }; + + template + struct check_after_handle_arity_3_const + { + template + struct get + {}; + }; + + template + struct check_after_handle_arity_3 + { + template + struct get + {}; + }; + + template + struct check_global_call_false { + template::type = true> + struct get + {}; + }; + + template + struct is_before_handle_arity_3_impl + { + template + static std::true_type f(typename check_before_handle_arity_3_const::template get*); + + template + static std::true_type f(typename check_before_handle_arity_3::template get*); + + template + static std::false_type f(...); + + public: + static const bool value = decltype(f(nullptr))::value; + }; + + template + struct is_after_handle_arity_3_impl + { + template + static std::true_type f(typename check_after_handle_arity_3_const::template get*); + + template + static std::true_type f(typename check_after_handle_arity_3::template get*); + + template + static std::false_type f(...); + + public: + static constexpr bool value = decltype(f(nullptr))::value; + }; + + template + typename std::enable_if::value>::type + before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.before_handle(req, res, ctx.template get(), ctx); + } + + template + typename std::enable_if::value>::type + before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.before_handle(req, res, ctx.template get()); + } + + template + typename std::enable_if::value>::type + after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.after_handle(req, res, ctx.template get(), ctx); + } + + template + typename std::enable_if::value>::type + after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) + { + mw.after_handle(req, res, ctx.template get()); + } + + + template class CallCriteria, // Checks if QueryMW should be called in this context + int N, typename Context, typename Container> + typename std::enable_if<(N < std::tuple_size::type>::value), bool>::type + middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx) + { + + using CurrentMW = typename std::tuple_element::type>::type; + + if (!CallCriteria::value) { + return middleware_call_helper(middlewares, req, res, ctx); + } + + using parent_context_t = typename Context::template partial; + before_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + if (res.is_completed()) + { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + return true; + } + + if (middleware_call_helper(middlewares, req, res, ctx)) + { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + return true; + } + + return false; + } + + template class CallCriteria, int N, typename Context, typename Container> + typename std::enable_if<(N >= std::tuple_size::type>::value), bool>::type + middleware_call_helper(Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/) + { + return false; + } + + template class CallCriteria, int N, typename Context, typename Container> + typename std::enable_if<(N < 0)>::type + after_handlers_call_helper(Container& /*middlewares*/, Context& /*context*/, request& /*req*/, response& /*res*/) + { + } + + template class CallCriteria, int N, typename Context, typename Container> + typename std::enable_if<(N == 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res) + { + using parent_context_t = typename Context::template partial; + using CurrentMW = typename std::tuple_element::type>::type; + if (CallCriteria::value) { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + } + } + + template class CallCriteria, int N, typename Context, typename Container> + typename std::enable_if<(N > 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res) + { + using parent_context_t = typename Context::template partial; + using CurrentMW = typename std::tuple_element::type>::type; + if (CallCriteria::value) { + after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); + } + after_handlers_call_helper(middlewares, ctx, req, res); + } + + // A CallCriteria that accepts only global middleware + template + struct middleware_call_criteria_only_global + { + template + static std::false_type f(typename check_global_call_false::template get*); + + template + static std::true_type f(...); + + static const bool value = decltype(f(nullptr))::value; + }; + + // wrapped_handler_call transparently wraps a handler call behind (req, res, args...) + template + typename std::enable_if::value>::type + wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args) + { + static_assert(std::is_same(), std::declval(), std::declval()...))>::value, + "Handler function with response argument should have void return type"); + + f(req, res, std::forward(args)...); + } + + template + typename std::enable_if::value + && !black_magic::is_callable::value + >::type + wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args) + { + static_assert(std::is_same(), std::declval(), std::declval()...))>::value, + "Handler function with response argument should have void return type"); + + f(req, res, std::forward(args)...); + } + + template + typename std::enable_if::value>::type + wrapped_handler_call(crow::request& /*req*/, crow::response& res, const F& f, Args&&... args) + { + static_assert(std::is_same(), std::declval()...))>::value, + "Handler function with response argument should have void return type"); + + f(res, std::forward(args)...); + } + + template + typename std::enable_if::value>::type + wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args) + { + static_assert(!std::is_same(), std::declval()...))>::value, + "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); + + res = crow::response(f(req, std::forward(args)...)); + res.end(); + } + + template + typename std::enable_if::value>::type + wrapped_handler_call(crow::request& /*req*/, crow::response& res, const F& f, Args&&... args) + { + static_assert(!std::is_same()...))>::value, + "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); + + res = crow::response(f(std::forward(args)...)); + res.end(); + } + + template + struct handler_middleware_wrapper + { + // CallCriteria bound to the current Middlewares pack + template + struct middleware_call_criteria + { + static constexpr bool value = black_magic::has_type>::value; + }; + + template + void operator()(crow::request& req, crow::response& res, Args&&... args) const + { + auto& ctx = *reinterpret_cast(req.middleware_context); + auto& container = *reinterpret_cast(req.middleware_container); + + bool completed = middleware_call_helper(container, req, res, ctx); + + if (completed) return; + + wrapped_handler_call(req, res, f, std::forward(args)...); + + after_handlers_call_helper< + middleware_call_criteria, + std::tuple_size::value - 1, + typename App::context_t, + typename App::mw_container_t>(container, ctx, req, res); + } + + F f; + }; + + template + struct handler_call_bridge + { + template + using check_app_contains = typename black_magic::has_type; + + static_assert(black_magic::all_true<(std::is_base_of::value)...>::value, + "Local middleware has to inherit crow::ILocalMiddleware"); + + static_assert(black_magic::all_true<(check_app_contains::value)...>::value, + "Local middleware has to be listed in app middleware"); + + template + void operator()(F&& f) const + { + auto wrapped = handler_middleware_wrapper {std::forward(f)}; + tptr->operator()(std::move(wrapped)); + } + + Route* tptr; + }; + + } // namespace detail +} // namespace crow diff --git a/include/crow/middleware_context.h b/include/crow/middleware_context.h index 3ac5b488b..6d9c3b122 100644 --- a/include/crow/middleware_context.h +++ b/include/crow/middleware_context.h @@ -34,23 +34,18 @@ namespace crow }; - - template - bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx); - - - template struct context : private partial_context //struct context : private Middlewares::context... // simple but less type-safe { - template + template class CallCriteria, int N, typename Context, typename Container> friend typename std::enable_if<(N == 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res); - template + template class CallCriteria, int N, typename Context, typename Container> friend typename std::enable_if<(N > 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res); - template - friend bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx); + template class CallCriteria, int N, typename Context, typename Container> + friend typename std::enable_if<(N < std::tuple_size::type>::value), bool>::type + middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx); template typename T::context& get() diff --git a/include/crow/routing.h b/include/crow/routing.h index d95efc33b..a77894476 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -15,6 +15,7 @@ #include "crow/logging.h" #include "crow/websocket.h" #include "crow/mustache.h" +#include "crow/middleware.h" namespace crow { @@ -44,7 +45,7 @@ namespace crow return {}; } - virtual void handle(const request&, response&, const routing_params&) = 0; + virtual void handle(request&, response&, const routing_params&) = 0; virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&) { res = response(404); @@ -109,7 +110,7 @@ namespace crow { H1& handler; const routing_params& params; - const request& req; + request& req; response& res; }; @@ -251,7 +252,7 @@ namespace crow typename handler_type_helper::type handler_; - void operator()(const request& req, response& res, const routing_params& params) + void operator()(request& req, response& res, const routing_params& params) { detail::routing_handler_call_helper::call< detail::routing_handler_call_helper::call_params< @@ -378,7 +379,7 @@ namespace crow void validate() override {} - void handle(const request&, response& res, const routing_params&) override + void handle(request&, response& res, const routing_params&) override { res = response(404); res.end(); @@ -490,7 +491,7 @@ namespace crow } } - void handle(const request& req, response& res, const routing_params& params) override + void handle(request& req, response& res, const routing_params& params) override { if (!custom_templates_base.empty()) mustache::set_base(custom_templates_base); @@ -518,7 +519,7 @@ namespace crow #else template #endif - std::function + std::function wrap(Func f, black_magic::seq) { #ifdef CROW_MSVC_WORKAROUND @@ -547,7 +548,7 @@ namespace crow } private: - std::function erased_handler_; + std::function erased_handler_; }; /// Default rule created when CROW_ROUTE is called. @@ -570,94 +571,18 @@ namespace crow } template - typename std::enable_if>::value, void>::type - operator()(Func&& f) - { - static_assert(black_magic::CallHelper>::value || - black_magic::CallHelper>::value, - "Handler type is mismatched with URL parameters"); - static_assert(!std::is_same()...))>::value, - "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); - - handler_ = ( -#ifdef CROW_CAN_USE_CPP14 - [f = std::move(f)] -#else - [f] -#endif - (const request&, response& res, Args... args) { - res = response(f(args...)); - res.end(); - }); - } - - template - typename std::enable_if< - !black_magic::CallHelper>::value && - black_magic::CallHelper>::value, - void>::type - operator()(Func&& f) - { - static_assert(black_magic::CallHelper>::value || - black_magic::CallHelper>::value, - "Handler type is mismatched with URL parameters"); - static_assert(!std::is_same(), std::declval()...))>::value, - "Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable"); - - handler_ = ( -#ifdef CROW_CAN_USE_CPP14 - [f = std::move(f)] -#else - [f] -#endif - (const crow::request& req, crow::response& res, Args... args) { - res = response(f(req, args...)); - res.end(); - }); - } - - template - typename std::enable_if< - !black_magic::CallHelper>::value && - !black_magic::CallHelper>::value && - black_magic::CallHelper>::value, - void>::type - operator()(Func&& f) - { - static_assert(black_magic::CallHelper>::value || - black_magic::CallHelper>::value, - "Handler type is mismatched with URL parameters"); - static_assert(std::is_same(), std::declval()...))>::value, - "Handler function with response argument should have void return type"); + void operator()(Func&& f) { handler_ = ( #ifdef CROW_CAN_USE_CPP14 [f = std::move(f)] #else [f] #endif - (const crow::request&, crow::response& res, Args... args) { - f(res, args...); + (crow::request& req, crow::response& res, Args... args) { + detail::wrapped_handler_call(req, res, f, std::forward(args)...); }); } - template - typename std::enable_if< - !black_magic::CallHelper>::value && - !black_magic::CallHelper>::value && - !black_magic::CallHelper>::value, - void>::type - operator()(Func&& f) - { - static_assert(black_magic::CallHelper>::value || - black_magic::CallHelper>::value || - black_magic::CallHelper>::value, - "Handler type is mismatched with URL parameters"); - static_assert(std::is_same(), std::declval(), std::declval()...))>::value, - "Handler function with response argument should have void return type"); - - handler_ = std::move(f); - } - template void operator()(std::string name, Func&& f) { @@ -665,7 +590,7 @@ namespace crow (*this).template operator()(std::forward(f)); } - void handle(const request& req, response& res, const routing_params& params) override + void handle(request& req, response& res, const routing_params& params) override { if (!custom_templates_base.empty()) mustache::set_base(custom_templates_base); @@ -673,17 +598,28 @@ namespace crow mustache::set_base("templates"); detail::routing_handler_call_helper::call< - detail::routing_handler_call_helper::call_params< - decltype(handler_)>, - 0, 0, 0, 0, - black_magic::S, - black_magic::S<>>()( - detail::routing_handler_call_helper::call_params< - decltype(handler_)>{handler_, params, req, res}); + detail::routing_handler_call_helper::call_params, + 0, 0, 0, 0, + black_magic::S, + black_magic::S<>>() + ( + detail::routing_handler_call_helper::call_params + {handler_, params, req, res} + ); + } + + /// Enable local middleware for this handler + template + crow::detail::handler_call_bridge, App, Middlewares...> + middlewares() + { + // the handler_call_bridge allows the functor to be placed directly after this function + // instead of wrapping it with more parentheses + return {this}; } private: - std::function handler_; + std::function handler_; }; const int RULE_SPECIAL_REDIRECT_SLASH = 1; @@ -1507,7 +1443,7 @@ namespace crow return std::string(); } - void handle(const request& req, response& res) + void handle(request& req, response& res) { HTTPMethod method_actual = req.method; if (req.method >= HTTPMethod::InternalMethodCount) diff --git a/include/crow/utility.h b/include/crow/utility.h index 782804971..9a382f866 100644 --- a/include/crow/utility.h +++ b/include/crow/utility.h @@ -237,6 +237,37 @@ namespace crow static constexpr bool value = sizeof(__test(0)) == sizeof(char); }; + // Check Tuple contains type T + template + struct has_type; + + template + struct has_type> : std::false_type {}; + + template + struct has_type> : has_type> {}; + + template + struct has_type> : std::true_type {}; + + // Check F is callable with Args + template + struct is_callable + { + template + static std::true_type __test(decltype(std::declval()(std::declval()...)) *) { return {}; } + + template + static std::false_type __test(...) { return {}; } + + static constexpr bool value = decltype(__test(nullptr))::value; + }; + + // Kind of fold expressions in C++11 + template + struct bool_pack; + template + using all_true = std::is_same, bool_pack>; template struct single_tag_to_type diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 10d49ccd1..ed739c266 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -44,10 +44,11 @@ TEST_CASE("Rule") r.validate(); response res; + request req; // executing handler CHECK(0 == x); - r.handle(request(), res, routing_params()); + r.handle(req, res, routing_params()); CHECK(1 == x); // registering handler with request argument @@ -60,7 +61,7 @@ TEST_CASE("Rule") // executing handler CHECK(1 == x); - r.handle(request(), res, routing_params()); + r.handle(req, res, routing_params()); CHECK(2 == x); } // Rule @@ -1374,6 +1375,56 @@ TEST_CASE("middleware_context") app.stop(); } // middleware_context +struct LocalSecretMiddleware : crow::ILocalMiddleware { + struct context + {}; + + void before_handle(request& /*req*/, response& res, context& /*ctx*/) + { + res.code = 403; + res.end(); + } + + void after_handle(request& /*req*/, response& /*res*/, context& /*ctx*/) + {} +}; + +TEST_CASE("local_middleware") +{ + App app; + + CROW_ROUTE(app, "/") + ([]() { + return "works!"; + }); + + CROW_ROUTE(app, "/secret") + .middlewares() + ([]() { + return "works!"; + }); + + app.validate(); + + // Local middleware is handled at router level, so we don't have to send requests manually + { + request req; + response res; + req.url = "/"; + app.handle(req, res); + CHECK(200 == res.code); + } + + { + request req; + response res; + req.url = "/secret"; + app.handle(req, res); + CHECK(403 == res.code); + } + +} // local_middleware + TEST_CASE("middleware_cookieparser") { static char buf[2048]; From 39e5eb427deb4f4c38f0fa69b89288f54cae9c0b Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 1 Feb 2022 22:50:01 +0300 Subject: [PATCH 02/10] Update docs --- docs/guides/middleware.md | 97 ++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/docs/guides/middleware.md b/docs/guides/middleware.md index 883b54500..7674eb203 100644 --- a/docs/guides/middleware.md +++ b/docs/guides/middleware.md @@ -1,29 +1,94 @@ -Any middleware requires following 3 members: -## struct context -Storing data for the middleware; can be read from another middleware or handlers +Middleware is used for altering and inspecting requests before and after the handler call. +Any middleware requires the following 3 members: + +* A context struct for storing the middleware data. +* A `before_handle` method, which is called before the handler. If `res.end()` is called, the operation is halted. +* A `after_handle` method, which is called after the handler. ## before_handle -Called before handling the request.
-If `res.end()` is called, the operation is halted. (`after_handle` will still be called)
-2 signatures:
-`#!cpp void before_handle(request& req, response& res, context& ctx)` - if you only need to access this middleware's context. +There are two possible signatures for before_handle + +1. if you only need to access this middleware's context. + +```cpp +void before_handle(request& req, response& res, context& ctx) +``` + +2. To get access to other middlewares context ``` cpp template -void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx) +void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx) +{ + auto other_ctx = all_ctx.template get(); +} ``` -You can access other middlewares' context by calling `#!cpp all_ctx.template get()`
-`#!cpp ctx == all_ctx.template get()` ## after_handle -Called after handling the request.
+There are two possible signatures for after_handle + +1. if you only need to access this middleware's context. -`#!cpp void after_handle(request& req, response& res, context& ctx)` +```cpp +void after_handle(request& req, response& res, context& ctx) +``` + +2. To get access to other middlewares context ``` cpp template -void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx) +void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx) +{ + auto other_ctx = all_ctx.template get(); +} +``` + +## Using middleware + +All middleware has to be registered in the Crow application and is enabled globally by default. + +```cpp +crow::App app; ``` -

-This was pulled from `cookie_parser.h`. Further Editing required, possibly use parts of [@ipkn's wiki page](https://github.com/ipkn/crow/wiki/Middleware). + +if you want to enable some middleware only for specific handlers, you have to extend it from `crow::ILocalMiddleware`. + +```cpp +struct LocalMiddleware : crow::ILocalMiddleware +{ +... +``` + +After this, you can enable it for specific handlers. + +```cpp +CROW_ROUTE(app, "/with_middleware") +.middlewares() +([]() { + return "Hello world!"; +}); +``` + +## Examples + +A local middleware that can be used to guard admin handlers + +```cpp +struct AdminAreaGuard : crow::ILocalMiddleware +{ + struct context + {}; + + void before_handle(crow::request& req, crow::response& res, context& ctx) + { + if (req.remote_ip_address != ADMIN_IP) + { + res.code = 403; + res.end(); + } + } + + void after_handle(crow::request& req, crow::response& res, context& ctx) + {} +}; +``` \ No newline at end of file From 69e9ad9c1e8d079c6fef6300f66e14b1e1f299a3 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 1 Feb 2022 23:21:07 +0300 Subject: [PATCH 03/10] Run clang format --- examples/example_middleware.cpp | 9 ++++---- include/crow/http_connection.h | 2 +- include/crow/middleware.h | 40 ++++++++++++++++++--------------- include/crow/routing.h | 16 ++++++------- include/crow/utility.h | 33 +++++++++++++++++---------- tests/unittest.cpp | 24 +++++++++++--------- 6 files changed, 68 insertions(+), 56 deletions(-) diff --git a/examples/example_middleware.cpp b/examples/example_middleware.cpp index f04264e1d..69cad6ac0 100644 --- a/examples/example_middleware.cpp +++ b/examples/example_middleware.cpp @@ -43,11 +43,10 @@ int main() }); CROW_ROUTE(app, "/secret") - // Enable SecretContentGuard for this handler - .middlewares() - ([]() { - return ""; - }); + // Enable SecretContentGuard for this handler + .middlewares()([]() { + return ""; + }); app.port(18080).run(); diff --git a/include/crow/http_connection.h b/include/crow/http_connection.h index 22a0f879f..168b83fa5 100644 --- a/include/crow/http_connection.h +++ b/include/crow/http_connection.h @@ -177,7 +177,7 @@ namespace crow req.io_service = &adaptor_.get_io_service(); detail::middleware_call_helper(*middlewares_, req, res, ctx_); + 0, decltype(ctx_), decltype(*middlewares_)>(*middlewares_, req, res, ctx_); if (!res.completed_) { diff --git a/include/crow/middleware.h b/include/crow/middleware.h index d527845f7..25745f2a5 100644 --- a/include/crow/middleware.h +++ b/include/crow/middleware.h @@ -9,10 +9,12 @@ #include #include -namespace crow { +namespace crow +{ - /// Local middleware should extend ILocalMiddleware - struct ILocalMiddleware { + /// Local middleware should extend ILocalMiddleware + struct ILocalMiddleware + { using call_global = std::false_type; }; @@ -51,7 +53,8 @@ namespace crow { }; template - struct check_global_call_false { + struct check_global_call_false + { template::type = true> struct get {}; @@ -117,16 +120,17 @@ namespace crow { mw.after_handle(req, res, ctx.template get()); } - - template class CallCriteria, // Checks if QueryMW should be called in this context - int N, typename Context, typename Container> + + template class CallCriteria, // Checks if QueryMW should be called in this context + int N, typename Context, typename Container> typename std::enable_if<(N < std::tuple_size::type>::value), bool>::type middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx) { using CurrentMW = typename std::tuple_element::type>::type; - if (!CallCriteria::value) { + if (!CallCriteria::value) + { return middleware_call_helper(middlewares, req, res, ctx); } @@ -165,7 +169,8 @@ namespace crow { { using parent_context_t = typename Context::template partial; using CurrentMW = typename std::tuple_element::type>::type; - if (CallCriteria::value) { + if (CallCriteria::value) + { after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); } } @@ -175,7 +180,8 @@ namespace crow { { using parent_context_t = typename Context::template partial; using CurrentMW = typename std::tuple_element::type>::type; - if (CallCriteria::value) { + if (CallCriteria::value) + { after_handler_call(std::get(middlewares), req, res, ctx, static_cast(ctx)); } after_handlers_call_helper(middlewares, ctx, req, res); @@ -206,9 +212,7 @@ namespace crow { } template - typename std::enable_if::value - && !black_magic::is_callable::value - >::type + typename std::enable_if::value && !black_magic::is_callable::value>::type wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args) { static_assert(std::is_same(), std::declval(), std::declval()...))>::value, @@ -266,7 +270,7 @@ namespace crow { auto& container = *reinterpret_cast(req.middleware_container); bool completed = middleware_call_helper(container, req, res, ctx); + 0, typename App::context_t, typename App::mw_container_t>(container, req, res, ctx); if (completed) return; @@ -284,20 +288,20 @@ namespace crow { template struct handler_call_bridge - { + { template using check_app_contains = typename black_magic::has_type; static_assert(black_magic::all_true<(std::is_base_of::value)...>::value, - "Local middleware has to inherit crow::ILocalMiddleware"); + "Local middleware has to inherit crow::ILocalMiddleware"); static_assert(black_magic::all_true<(check_app_contains::value)...>::value, - "Local middleware has to be listed in app middleware"); + "Local middleware has to be listed in app middleware"); template void operator()(F&& f) const { - auto wrapped = handler_middleware_wrapper {std::forward(f)}; + auto wrapped = handler_middleware_wrapper{std::forward(f)}; tptr->operator()(std::move(wrapped)); } diff --git a/include/crow/routing.h b/include/crow/routing.h index a77894476..155cc4525 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -571,7 +571,8 @@ namespace crow } template - void operator()(Func&& f) { + void operator()(Func&& f) + { handler_ = ( #ifdef CROW_CAN_USE_CPP14 [f = std::move(f)] @@ -598,14 +599,11 @@ namespace crow mustache::set_base("templates"); detail::routing_handler_call_helper::call< - detail::routing_handler_call_helper::call_params, - 0, 0, 0, 0, - black_magic::S, - black_magic::S<>>() - ( - detail::routing_handler_call_helper::call_params - {handler_, params, req, res} - ); + detail::routing_handler_call_helper::call_params, + 0, 0, 0, 0, + black_magic::S, + black_magic::S<>>()( + detail::routing_handler_call_helper::call_params{handler_, params, req, res}); } /// Enable local middleware for this handler diff --git a/include/crow/utility.h b/include/crow/utility.h index 9a382f866..abb30b529 100644 --- a/include/crow/utility.h +++ b/include/crow/utility.h @@ -238,27 +238,36 @@ namespace crow }; // Check Tuple contains type T - template + template struct has_type; - template - struct has_type> : std::false_type {}; + template + struct has_type> : std::false_type + {}; - template - struct has_type> : has_type> {}; + template + struct has_type> : has_type> + {}; - template - struct has_type> : std::true_type {}; + template + struct has_type> : std::true_type + {}; // Check F is callable with Args - template + template struct is_callable { - template - static std::true_type __test(decltype(std::declval()(std::declval()...)) *) { return {}; } + template + static std::true_type __test(decltype(std::declval()(std::declval()...))*) + { + return {}; + } - template - static std::false_type __test(...) { return {}; } + template + static std::false_type __test(...) + { + return {}; + } static constexpr bool value = decltype(__test(nullptr))::value; }; diff --git a/tests/unittest.cpp b/tests/unittest.cpp index ed739c266..ffa97d23f 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -1142,12 +1142,14 @@ TEST_CASE("template_basic") TEST_CASE("template_function") { - auto t = crow::mustache::compile("attack of {{func}}"); - crow::mustache::context ctx; - ctx["name"] = "killer tomatoes"; - ctx["func"] = [&](std::string){return std::string("{{name}}, IN SPACE!");}; - auto result = t.render(ctx); - CHECK("attack of killer tomatoes, IN SPACE!" == result); + auto t = crow::mustache::compile("attack of {{func}}"); + crow::mustache::context ctx; + ctx["name"] = "killer tomatoes"; + ctx["func"] = [&](std::string) { + return std::string("{{name}}, IN SPACE!"); + }; + auto result = t.render(ctx); + CHECK("attack of killer tomatoes, IN SPACE!" == result); } TEST_CASE("template_load") @@ -1375,7 +1377,8 @@ TEST_CASE("middleware_context") app.stop(); } // middleware_context -struct LocalSecretMiddleware : crow::ILocalMiddleware { +struct LocalSecretMiddleware : crow::ILocalMiddleware +{ struct context {}; @@ -1399,10 +1402,9 @@ TEST_CASE("local_middleware") }); CROW_ROUTE(app, "/secret") - .middlewares() - ([]() { - return "works!"; - }); + .middlewares()([]() { + return "works!"; + }); app.validate(); From 5d6db06706d8557f28471bd6cbdb20c9ea627f78 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Wed, 2 Feb 2022 17:37:19 +0300 Subject: [PATCH 04/10] Add local middleware after handlers to request handler --- include/crow/http_response.h | 8 ++++++++ include/crow/middleware.h | 17 +++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/include/crow/http_response.h b/include/crow/http_response.h index 505c7c3f1..fbdf0fc5d 100644 --- a/include/crow/http_response.h +++ b/include/crow/http_response.h @@ -19,12 +19,20 @@ namespace crow template class Connection; + namespace detail { + template + struct handler_middleware_wrapper; + } // namespace detail + /// HTTP response struct response { template friend class crow::Connection; + template + friend struct crow::detail::handler_middleware_wrapper; + int code{200}; ///< The Status code for the response. std::string body; ///< The actual payload containing the response data. ci_map headers; ///< HTTP headers. diff --git a/include/crow/middleware.h b/include/crow/middleware.h index 25745f2a5..e6a2cef27 100644 --- a/include/crow/middleware.h +++ b/include/crow/middleware.h @@ -274,13 +274,18 @@ namespace crow if (completed) return; - wrapped_handler_call(req, res, f, std::forward(args)...); + auto glob_completion_handler = std::move(res.complete_request_handler_); + auto completion_handler = [&ctx, &container, &req, &res, &glob_completion_handler] { + after_handlers_call_helper< + middleware_call_criteria, + std::tuple_size::value - 1, + typename App::context_t, + typename App::mw_container_t>(container, ctx, req, res); + glob_completion_handler(); + }; + res.complete_request_handler_ = std::move(completion_handler); - after_handlers_call_helper< - middleware_call_criteria, - std::tuple_size::value - 1, - typename App::context_t, - typename App::mw_container_t>(container, ctx, req, res); + wrapped_handler_call(req, res, f, std::forward(args)...); } F f; From f1dd5cc78acd5c5ca5f7d206ad3ec723527bb008 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 8 Feb 2022 19:37:24 +0300 Subject: [PATCH 05/10] Add example_middleware to build --- examples/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ed3d6031c..27f3d9bde 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -79,6 +79,10 @@ add_executable(example_blueprint example_blueprint.cpp) add_warnings_optimizations(example_blueprint) target_link_libraries(example_blueprint PUBLIC Crow::Crow) +add_executable(example_middleware example_middleware.cpp) +add_warnings_optimizations(example_middleware) +target_link_libraries(example_middleware PUBLIC Crow::Crow) + if(MSVC) add_executable(example_vs example_vs.cpp) add_warnings_optimizations(example_vs) From 4f4e12c91aa4fac71cccd2f61188445fa1283fc6 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 8 Feb 2022 20:11:02 +0300 Subject: [PATCH 06/10] Add CROW_MIDDLEWARES macro --- docs/guides/middleware.md | 2 +- examples/example_middleware.cpp | 2 +- include/crow/app.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/guides/middleware.md b/docs/guides/middleware.md index 7674eb203..abfea9f7b 100644 --- a/docs/guides/middleware.md +++ b/docs/guides/middleware.md @@ -63,7 +63,7 @@ After this, you can enable it for specific handlers. ```cpp CROW_ROUTE(app, "/with_middleware") -.middlewares() +.CROW_MIDDLEWARES(app, LocalMiddleware) ([]() { return "Hello world!"; }); diff --git a/examples/example_middleware.cpp b/examples/example_middleware.cpp index 69cad6ac0..e53381724 100644 --- a/examples/example_middleware.cpp +++ b/examples/example_middleware.cpp @@ -44,7 +44,7 @@ int main() CROW_ROUTE(app, "/secret") // Enable SecretContentGuard for this handler - .middlewares()([]() { + .CROW_MIDDLEWARES(app, SecretContentGuard)([]() { return ""; }); diff --git a/include/crow/app.h b/include/crow/app.h index f88a3bdc9..26d850d58 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -29,6 +29,7 @@ #else #define CROW_ROUTE(app, url) app.route(url) #define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged(url) +#define CROW_MIDDLEWARES(app, ...) middlewares() #endif #define CROW_CATCHALL_ROUTE(app) app.catchall_route() #define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule() From 80bc1cf00a23472cb5a1021037e6dcc877aaa077 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 8 Feb 2022 20:18:49 +0300 Subject: [PATCH 07/10] Fix clang format --- include/crow/http_response.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/crow/http_response.h b/include/crow/http_response.h index fbdf0fc5d..9862a6a41 100644 --- a/include/crow/http_response.h +++ b/include/crow/http_response.h @@ -19,7 +19,8 @@ namespace crow template class Connection; - namespace detail { + namespace detail + { template struct handler_middleware_wrapper; } // namespace detail From 0b1eb12f60dbe2cf06d98b6dabf79a60ec913dba Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 8 Feb 2022 21:15:25 +0300 Subject: [PATCH 08/10] Fix nonempty completion handler before middleware call --- include/crow/middleware.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/include/crow/middleware.h b/include/crow/middleware.h index e6a2cef27..c8b10fe4e 100644 --- a/include/crow/middleware.h +++ b/include/crow/middleware.h @@ -269,13 +269,19 @@ namespace crow auto& ctx = *reinterpret_cast(req.middleware_context); auto& container = *reinterpret_cast(req.middleware_container); - bool completed = middleware_call_helper(container, req, res, ctx); + auto glob_completion_handler = std::move(res.complete_request_handler_); + res.complete_request_handler_ = [] {}; - if (completed) return; + middleware_call_helper(container, req, res, ctx); - auto glob_completion_handler = std::move(res.complete_request_handler_); - auto completion_handler = [&ctx, &container, &req, &res, &glob_completion_handler] { + if (res.completed_) + { + glob_completion_handler(); + return; + } + + res.complete_request_handler_ = [&ctx, &container, &req, &res, &glob_completion_handler] { after_handlers_call_helper< middleware_call_criteria, std::tuple_size::value - 1, @@ -283,7 +289,6 @@ namespace crow typename App::mw_container_t>(container, ctx, req, res); glob_completion_handler(); }; - res.complete_request_handler_ = std::move(completion_handler); wrapped_handler_call(req, res, f, std::forward(args)...); } From a5ba69f5a42f6975ac61b79db7159403805e0db8 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 8 Feb 2022 21:57:33 +0300 Subject: [PATCH 09/10] Update test for completion handler fix --- tests/unittest.cpp | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/tests/unittest.cpp b/tests/unittest.cpp index ffa97d23f..3cd09774c 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -1394,6 +1394,8 @@ struct LocalSecretMiddleware : crow::ILocalMiddleware TEST_CASE("local_middleware") { + static char buf[2048]; + App app; CROW_ROUTE(app, "/") @@ -1408,23 +1410,36 @@ TEST_CASE("local_middleware") app.validate(); - // Local middleware is handled at router level, so we don't have to send requests manually + auto _ = async(launch::async, + [&] { + app.bindaddr(LOCALHOST_ADDRESS).port(45451).run(); + }); + app.wait_for_server_start(); + asio::io_service is; + { - request req; - response res; - req.url = "/"; - app.handle(req, res); - CHECK(200 == res.code); + asio::ip::tcp::socket c(is); + c.connect(asio::ip::tcp::endpoint( + asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451)); + c.send(asio::buffer("GET /\r\n\r\n")); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + CHECK(std::string(buf).find("200") != std::string::npos); } { - request req; - response res; - req.url = "/secret"; - app.handle(req, res); - CHECK(403 == res.code); + asio::ip::tcp::socket c(is); + c.connect(asio::ip::tcp::endpoint( + asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451)); + c.send(asio::buffer("GET /secret\r\n\r\n")); + c.receive(asio::buffer(buf, 2048)); + c.close(); + + CHECK(std::string(buf).find("403") != std::string::npos); } + app.stop(); } // local_middleware TEST_CASE("middleware_cookieparser") From b9dded57e8f3fbc86562254f9ce90c72fb22bd67 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Sun, 13 Feb 2022 14:35:11 +0300 Subject: [PATCH 10/10] Remove is_callable impl --- include/crow/utility.h | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/include/crow/utility.h b/include/crow/utility.h index abb30b529..fc1462b4f 100644 --- a/include/crow/utility.h +++ b/include/crow/utility.h @@ -258,16 +258,10 @@ namespace crow struct is_callable { template - static std::true_type __test(decltype(std::declval()(std::declval()...))*) - { - return {}; - } + static std::true_type __test(decltype(std::declval()(std::declval()...))*); template - static std::false_type __test(...) - { - return {}; - } + static std::false_type __test(...); static constexpr bool value = decltype(__test(nullptr))::value; };