-
-
Notifications
You must be signed in to change notification settings - Fork 383
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
Local middleware #327
Conversation
@@ -68,7 +68,7 @@ namespace crow | |||
} | |||
|
|||
/// Process the request and generate a response for it | |||
void handle(const request& req, response& res) |
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
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
move to middleware.h
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
store middleware in request
|
||
namespace detail | ||
{ | ||
template<typename MW> |
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.
from http_connection
}; | ||
|
||
template<typename MW> | ||
struct check_global_call_false |
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.
check if middleware MW should be not called globally
}; | ||
|
||
template<typename Route, typename App, typename... Middlewares> | ||
struct handler_call_bridge |
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.
makes registering middleware less verbose
@@ -44,7 +45,7 @@ namespace crow | |||
return {}; | |||
} | |||
|
|||
virtual void handle(const request&, response&, const routing_params&) = 0; |
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.
const issue
}); | ||
} | ||
|
||
template<typename Func> |
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.
all this now uses detail::wrapped_handler_call
@@ -237,6 +237,46 @@ namespace crow | |||
static constexpr bool value = sizeof(__test<F, Args...>(0)) == sizeof(char); | |||
}; | |||
|
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.
some things for black magic I didn't find
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.
I've noticed a couple things that I can't place on lines of code:
The middleware example isn't added to examples/CMakeLists.txt
, I did it locally as such:
add_executable(example_middleware example_middleware.cpp)
add_warnings_optimizations(example_middleware)
target_link_libraries(example_middleware PUBLIC Crow::Crow)
The after_handle
does not affect the response (which it should), this only happens with local middleware. (The handle is called, but the response doesn't change)
At the time of writing I haven't touched dynamic rules. I've never used them and it seems like they are only part of the CROW_MSVC_WORKAROUND.
I've seen multiple places where dynamic rules are used beyond old MSVC support (see #164 and #308)
I also don't know whether CatchAll routes should support middleware, so I've restricted it to only TaggedRules.
I would think it's a good idea if they did, although it's not crucial, we can probably create an issue for it to address later.
As you may have noticed, I didn't go into Most of the code relating to templates, this is because While I did understand what's happening in a general sense (Thanks to the very helpful descriptions you wrote). I have no clue how it's actually working, it might be best if someone with a better understanding took a look at them. Besides that I'll still try to understand what's going on to properly review the template code. Any resources, material, or descriptions that are helpful would be greatly appreciated!
Thank you again for the work you've done and I'm sorry for taking so long to get this review out!
--- include/crow/http_response.h (before formatting)
+++ include/crow/http_response.h (after formatting)
@@ -19,7 +19,8 @@
template<typename Adaptor, typename Handler, typename... Middlewares>
class Connection;
- namespace detail {
+ namespace detail
+ {
template<typename F, typename App, typename... Middlewares>
struct handler_middleware_wrapper;
} // namespace detail |
One thing I noticed with the latest commit is that if a Edit: This is interesting, if a middleware registered first in the app, it's |
|
I reproduced your issue and it indeed looks like a bug. This is because the complete_request_handler_ has to be empty before calling any middleware so that such cases won't arise. It should now be identical to http_connections logic. |
The |
This is because it depends now on the full request chain. So the local middleware test has to use real requests like all the other middleware tests do. Sorry for pushing separately 😓 |
No worries, as long as the formatting bot doesn't write a 2000 page report it's all fine 😆 |
From С++ point of view (and my) looks nice. |
I'm sorry it took me this long to look at the latest changes, from what I can see this is the new logic: middlewares execute their before and after handles based on the order of registration in the app, if a This is more about the design of the framework rather than the actual code, but I do think this is a problem that needs to be addressed. But that's way out of the scope of this PR, I'll open a separate issue to discuss it. |
The current crow middleware logic seems quite reasonable to me. Middleware that hasn't been called generally has no cleanup to perform. For basic response rewriting it should be registered first. if I recall correctly, we decided to cover the remaining route types in another PR. Are there any issues left to be addressed? |
Sorry for the late response. There shouldn't be any issues, I'll just do one last review (and hopefully understand the template logic) and merge the PR |
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.
Looks all good, though there's some stuff to clarify in the docs (primarily the load order, lack of support for dynamic requests, and what happens when a request is cancelled). I'll handle those in a later PR.
I do expect some bugs to creep up after this is merged (as they always do). And while I understand almost all of the code, I would be very thankful if you stuck around in case there was something that myself or the other developers couldn't fix.
Thanks again for the work you've done, and thanks for bearing with the review process.
No problem, I'll stick around for support and further enhancements 😄 |
Hi! I've restructured middleware invocation and added per-handler middleware as discussed in the issue.
What it looks like
It's fully backward compatible and all (including local) middleware as previously has to be listed in the App<>.
Local middleware extends
crow::ILocalMiddleware
and is not called by default. It has to be enabled with amiddleware()
call on the rule.What I've changed
I'll try to briefly explain my changes and decisions here, but I'll also leave some PR comments.
This is the current call chain for all requests:
Getting type info up the chain
To allow wrapping the handler directly with middleware we'd have to somehow get the type information to it. This could be done in two ways:
The first approach is somewhat more complex. It'd would seem more reasonable to move global middleware invocation from something as low-level as an http connection further up the chain to the router and restructure it.
The second one is straightforward, but has some tradeoffs:
I've chosen the second approach as it seems more likely to be fully implemented 😁
Filtering middleware at compile time
http_connection
was responsible for handling all middleware, including template magic. I've moved most of it tomiddleware.h
, some parts toutility.h
.To reuse the same code for calling global and local middleware, I've generalized the
middleware_call_helper
to include aCallCriteria
, which can be used to determine whether a specific middleware should be called.Now a handlers criteria simply checks whether the queried middleware is present in its enabled middleware.
The
http_connection
still handles global middleware and calls all middleware withmiddleware_call_criteria_only_global
.I've also refactored
middleware_call_helper
to use tuple indexing (likeafter_handlers_call_helper
does) instead of recursively unpacking the parameter pack, as it is not available to the handler in its raw form.Transparently wrapping handlers
Router rules already do it and hide all handlers behind a
std::function<void(const request, response, args...)>
.Handlers with middleware are wrapped in
handler_middleware_wrapper
, which extracts the middleware context from the request, and calls all the local middlewares, which require both a mutable request and response. This means that we have to get a mutable request all the way through the chain, so I've removed the interfering const qualifiers.I've moved the handler wrapping logic from
TaggedRule::operator(Func&&)
towrapped_handler_call
inmiddleware.h
. I've also refactored it to use a more elegantis_callable
sfinae check and allow mutable requests as parameters.To sum up
The call chain now looks the following:
This is how a handler is wrapped:
Issues
Global middleware is called before local, there is no way to intertwine them. To support this all middleware invocation has to be moved directly to the handler, oh, and all handlers had to be wrapped. I can't guess whether this is a big tradeoff, because the solution to this is quite complex.
The local invocation order is defined not in the
middleware()
call, but in the App params. This can be fixed, but requires restructuring all middleware call helpers.At the time of writing I haven't touched dynamic rules. I've never used them and it seems like they are only part of the CROW_MSVC_WORKAROUND.
I also don't know whether CatchAll routes should support middleware, so I've restricted it to only TaggedRules.
Testing
I've included a small example and unit test. I've tested it with both gcc/clang on my small crow projects.
Would be great if you'd test it out.
The changes are somewhat verbose in some parts, but therefore c++11 compliant.