-
-
Notifications
You must be signed in to change notification settings - Fork 382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Local middleware #327
Merged
Merged
Local middleware #327
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
e60714c
Separate middleware for handlers
dranikpg 39e5eb4
Update docs
dranikpg 69e9ad9
Run clang format
dranikpg 5d6db06
Add local middleware after handlers to request handler
dranikpg f1dd5cc
Add example_middleware to build
dranikpg 4f4e12c
Add CROW_MIDDLEWARES macro
dranikpg 80bc1cf
Fix clang format
dranikpg 0b1eb12
Fix nonempty completion handler before middleware call
dranikpg a5ba69f
Update test for completion handler fix
dranikpg b9dded5
Remove is_callable impl
dranikpg 1c98bbb
Merge branch 'master' into local-middleware
The-EDev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.<br> | ||
If `res.end()` is called, the operation is halted. (`after_handle` will still be called)<br> | ||
2 signatures:<br> | ||
`#!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 <typename AllContext> | ||
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<OtherMiddleware>(); | ||
} | ||
``` | ||
You can access other middlewares' context by calling `#!cpp all_ctx.template get<MW>()`<br> | ||
`#!cpp ctx == all_ctx.template get<CurrentMiddleware>()` | ||
|
||
|
||
## after_handle | ||
Called after handling the request.<br> | ||
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 <typename AllContext> | ||
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<OtherMiddleware>(); | ||
} | ||
``` | ||
|
||
## Using middleware | ||
|
||
All middleware has to be registered in the Crow application and is enabled globally by default. | ||
|
||
```cpp | ||
crow::App<FirstMiddleware, SecondMiddleware> app; | ||
``` | ||
<br><br> | ||
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) | ||
{} | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<RequestLogger, SecretContentGuard> 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move to middleware.h |
||
{ | ||
template<typename MW> | ||
struct check_before_handle_arity_3_const | ||
{ | ||
template<typename T, void (T::*)(request&, response&, typename MW::context&) const = &T::before_handle> | ||
struct get | ||
{}; | ||
}; | ||
|
||
template<typename MW> | ||
struct check_before_handle_arity_3 | ||
{ | ||
template<typename T, void (T::*)(request&, response&, typename MW::context&) = &T::before_handle> | ||
struct get | ||
{}; | ||
}; | ||
|
||
template<typename MW> | ||
struct check_after_handle_arity_3_const | ||
{ | ||
template<typename T, void (T::*)(request&, response&, typename MW::context&) const = &T::after_handle> | ||
struct get | ||
{}; | ||
}; | ||
|
||
template<typename MW> | ||
struct check_after_handle_arity_3 | ||
{ | ||
template<typename T, void (T::*)(request&, response&, typename MW::context&) = &T::after_handle> | ||
struct get | ||
{}; | ||
}; | ||
|
||
template<typename T> | ||
struct is_before_handle_arity_3_impl | ||
{ | ||
template<typename C> | ||
static std::true_type f(typename check_before_handle_arity_3_const<T>::template get<C>*); | ||
|
||
template<typename C> | ||
static std::true_type f(typename check_before_handle_arity_3<T>::template get<C>*); | ||
|
||
template<typename C> | ||
static std::false_type f(...); | ||
|
||
public: | ||
static const bool value = decltype(f<T>(nullptr))::value; | ||
}; | ||
|
||
template<typename T> | ||
struct is_after_handle_arity_3_impl | ||
{ | ||
template<typename C> | ||
static std::true_type f(typename check_after_handle_arity_3_const<T>::template get<C>*); | ||
|
||
template<typename C> | ||
static std::true_type f(typename check_after_handle_arity_3<T>::template get<C>*); | ||
|
||
template<typename C> | ||
static std::false_type f(...); | ||
|
||
public: | ||
static const bool value = decltype(f<T>(nullptr))::value; | ||
}; | ||
|
||
template<typename MW, typename Context, typename ParentContext> | ||
typename std::enable_if<!is_before_handle_arity_3_impl<MW>::value>::type | ||
before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) | ||
{ | ||
mw.before_handle(req, res, ctx.template get<MW>(), ctx); | ||
} | ||
|
||
template<typename MW, typename Context, typename ParentContext> | ||
typename std::enable_if<is_before_handle_arity_3_impl<MW>::value>::type | ||
before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) | ||
{ | ||
mw.before_handle(req, res, ctx.template get<MW>()); | ||
} | ||
|
||
template<typename MW, typename Context, typename ParentContext> | ||
typename std::enable_if<!is_after_handle_arity_3_impl<MW>::value>::type | ||
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) | ||
{ | ||
mw.after_handle(req, res, ctx.template get<MW>(), ctx); | ||
} | ||
|
||
template<typename MW, typename Context, typename ParentContext> | ||
typename std::enable_if<is_after_handle_arity_3_impl<MW>::value>::type | ||
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/) | ||
{ | ||
mw.after_handle(req, res, ctx.template get<MW>()); | ||
} | ||
|
||
template<int N, typename Context, typename Container, typename CurrentMW, typename... Middlewares> | ||
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx) | ||
{ | ||
using parent_context_t = typename Context::template partial<N - 1>; | ||
before_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx)); | ||
|
||
if (res.is_completed()) | ||
{ | ||
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx)); | ||
return true; | ||
} | ||
|
||
if (middleware_call_helper<N + 1, Context, Container, Middlewares...>(middlewares, req, res, ctx)) | ||
{ | ||
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx)); | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
template<int N, typename Context, typename Container> | ||
bool middleware_call_helper(Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/) | ||
{ | ||
return false; | ||
} | ||
|
||
template<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<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<N - 1>; | ||
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type; | ||
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx)); | ||
} | ||
|
||
template<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<N - 1>; | ||
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type; | ||
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx)); | ||
after_handlers_call_helper<N - 1, Context, Container>(middlewares, ctx, req, res); | ||
} | ||
} // namespace detail | ||
|
||
#ifdef CROW_ENABLE_DEBUG | ||
static std::atomic<int> connectionCount; | ||
|
@@ -316,8 +173,11 @@ namespace crow | |
|
||
ctx_ = detail::context<Middlewares...>(); | ||
req.middleware_context = static_cast<void*>(&ctx_); | ||
req.middleware_container = static_cast<void*>(middlewares_); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. store middleware in request |
||
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<detail::middleware_call_criteria_only_global, | ||
0, decltype(ctx_), decltype(*middlewares_)>(*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<int>(sizeof...(Middlewares)) - 1), | ||
decltype(ctx_), | ||
decltype(*middlewares_)>(*middlewares_, ctx_, req_, res); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove const qualifiers up to rule->handle() to allow passing mutable requests into local middleware