diff --git a/docs/guides/middleware.md b/docs/guides/middleware.md index 883b54500..abfea9f7b 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") +.CROW_MIDDLEWARES(app, LocalMiddleware) +([]() { + 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 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) diff --git a/examples/example_middleware.cpp b/examples/example_middleware.cpp new file mode 100644 index 000000000..e53381724 --- /dev/null +++ b/examples/example_middleware.cpp @@ -0,0 +1,54 @@ +#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 + .CROW_MIDDLEWARES(app, SecretContentGuard)([]() { + 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..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() @@ -68,7 +69,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 +395,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..168b83fa5 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/http_response.h b/include/crow/http_response.h index bfae40517..473acb611 100644 --- a/include/crow/http_response.h +++ b/include/crow/http_response.h @@ -19,12 +19,21 @@ 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 new file mode 100644 index 000000000..c8b10fe4e --- /dev/null +++ b/include/crow/middleware.h @@ -0,0 +1,322 @@ +#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); + + auto glob_completion_handler = std::move(res.complete_request_handler_); + res.complete_request_handler_ = [] {}; + + middleware_call_helper(container, req, res, ctx); + + 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, + typename App::context_t, + typename App::mw_container_t>(container, ctx, req, res); + glob_completion_handler(); + }; + + wrapped_handler_call(req, res, f, std::forward(args)...); + } + + 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..155cc4525 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,19 @@ namespace crow } template - typename std::enable_if>::value, void>::type - operator()(Func&& f) + void 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(); + (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 +591,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 +599,25 @@ namespace crow mustache::set_base("templates"); detail::routing_handler_call_helper::call< - detail::routing_handler_call_helper::call_params< - decltype(handler_)>, + detail::routing_handler_call_helper::call_params, 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{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 +1441,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 3dd63f9a3..460f89750 100644 --- a/include/crow/utility.h +++ b/include/crow/utility.h @@ -237,6 +237,40 @@ 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()...))*); + + template + static std::false_type __test(...); + + 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 34c6be839..6dc3353eb 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 @@ -1141,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") @@ -1374,6 +1377,71 @@ 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") +{ + static char buf[2048]; + + App app; + + CROW_ROUTE(app, "/") + ([]() { + return "works!"; + }); + + CROW_ROUTE(app, "/secret") + .middlewares()([]() { + return "works!"; + }); + + app.validate(); + + auto _ = async(launch::async, + [&] { + app.bindaddr(LOCALHOST_ADDRESS).port(45451).run(); + }); + app.wait_for_server_start(); + asio::io_service is; + + { + 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); + } + + { + 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") { static char buf[2048];