From e60714c0b2e5ea4656916a5b658a8d5eada98a3a Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Tue, 1 Feb 2022 16:31:05 +0300 Subject: [PATCH] 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"); - + void operator()(Func&& f) { 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(); + (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, - 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"); - handler_ = ( -#ifdef CROW_CAN_USE_CPP14 - [f = std::move(f)] -#else - [f] -#endif - (const crow::request&, crow::response& res, Args... args) { - f(res, 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];