Blueprint middleware prototype

This commit is contained in:
Vladislav Oleshko 2022-04-11 16:01:27 +03:00
parent 1e73642535
commit 3b9e522523
8 changed files with 255 additions and 125 deletions

View File

@ -71,7 +71,7 @@ namespace crow
/// Process the request and generate a response for it
void handle(request& req, response& res)
{
router_.handle(req, res);
router_.handle<self_t>(req, res);
}
/// Create a dynamic route using a rule (**Use CROW_ROUTE instead**)

View File

@ -176,7 +176,7 @@ namespace crow
req.io_service = &adaptor_.get_io_service();
detail::middleware_call_helper<detail::middleware_call_criteria_only_global,
0, decltype(ctx_), decltype(*middlewares_)>(*middlewares_, req, res, ctx_);
0, decltype(ctx_), decltype(*middlewares_)>({}, *middlewares_, req, res, ctx_);
if (!res.completed_)
{
@ -213,7 +213,7 @@ namespace crow
detail::middleware_call_criteria_only_global,
(static_cast<int>(sizeof...(Middlewares)) - 1),
decltype(ctx_),
decltype(*middlewares_)>(*middlewares_, ctx_, req_, res);
decltype(*middlewares_)>({}, *middlewares_, ctx_, req_, res);
}
#ifdef CROW_ENABLE_COMPRESSION
if (handler_->compression_used())

View File

@ -19,11 +19,7 @@ namespace crow
template<typename Adaptor, typename Handler, typename... Middlewares>
class Connection;
namespace detail
{
template<typename F, typename App, typename... Middlewares>
struct handler_middleware_wrapper;
} // namespace detail
class Router;
/// HTTP response
struct response
@ -31,8 +27,7 @@ namespace crow
template<typename Adaptor, typename Handler, typename... Middlewares>
friend class crow::Connection;
template<typename F, typename App, typename... Middlewares>
friend struct crow::detail::handler_middleware_wrapper;
friend class Router;
int code{200}; ///< The Status code for the response.
std::string body; ///< The actual payload containing the response data.

View File

@ -92,6 +92,18 @@ namespace crow
static constexpr bool value = decltype(f<T>(nullptr))::value;
};
template<typename MW>
struct is_middleware_global
{
template<typename C>
static std::false_type f(typename check_global_call_false<MW>::template get<C>*);
template<typename C>
static std::true_type f(...);
static const bool value = decltype(f<MW>(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*/)
@ -121,17 +133,17 @@ namespace crow
}
template<template<typename QueryMW> class CallCriteria, // Checks if QueryMW should be called in this context
template<typename CallCriteria,
int N, typename Context, typename Container>
typename std::enable_if<(N < std::tuple_size<typename std::remove_reference<Container>::type>::value), bool>::type
middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
middleware_call_helper(const CallCriteria& cc, Container& middlewares, request& req, response& res, Context& ctx)
{
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
if (!CallCriteria<CurrentMW>::value)
if (!cc.template enabled<CurrentMW>(N))
{
return middleware_call_helper<CallCriteria, N + 1, Context, Container>(middlewares, req, res, ctx);
return middleware_call_helper<CallCriteria, N + 1, Context, Container>(cc, middlewares, req, res, ctx);
}
using parent_context_t = typename Context::template partial<N - 1>;
@ -142,7 +154,7 @@ namespace crow
return true;
}
if (middleware_call_helper<CallCriteria, N + 1, Context, Container>(middlewares, req, res, ctx))
if (middleware_call_helper<CallCriteria, N + 1, Context, Container>(cc, 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;
@ -151,53 +163,50 @@ namespace crow
return false;
}
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
template<typename CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N >= std::tuple_size<typename std::remove_reference<Container>::type>::value), bool>::type
middleware_call_helper(Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/)
middleware_call_helper(const CallCriteria& /*cc*/, Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/)
{
return false;
}
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
template<typename 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*/)
after_handlers_call_helper(const CallCriteria& /*cc*/, Container& /*middlewares*/, Context& /*context*/, request& /*req*/, response& /*res*/)
{
}
template<template<typename QueryMW> 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)
template<typename CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N == 0)>::type after_handlers_call_helper(const CallCriteria& cc, 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;
if (CallCriteria<CurrentMW>::value)
if (cc.template enabled<CurrentMW>(N))
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
}
}
template<template<typename QueryMW> 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)
template<typename CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N > 0)>::type after_handlers_call_helper(const CallCriteria& cc, 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;
if (CallCriteria<CurrentMW>::value)
if (cc.template enabled<CurrentMW>(N))
{
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<CallCriteria, N - 1, Context, Container>(middlewares, ctx, req, res);
after_handlers_call_helper<CallCriteria, N - 1, Context, Container>(cc, middlewares, ctx, req, res);
}
// A CallCriteria that accepts only global middleware
template<typename MW>
struct middleware_call_criteria_only_global
{
template<typename C>
static std::false_type f(typename check_global_call_false<MW>::template get<C>*);
template<typename C>
static std::true_type f(...);
static const bool value = decltype(f<MW>(nullptr))::value;
template<typename MW>
constexpr bool enabled(int) const
{
return is_middleware_global<MW>::value;
}
};
// wrapped_handler_call transparently wraps a handler call behind (req, res, args...)
@ -253,69 +262,14 @@ namespace crow
res.end();
}
template<typename F, typename App, typename... Middlewares>
struct handler_middleware_wrapper
struct middleware_call_criteria_dynamic
{
// CallCriteria bound to the current Middlewares pack
template<typename MW>
struct middleware_call_criteria
template<typename>
const bool enabled(int i) const
{
static constexpr bool value = black_magic::has_type<MW, std::tuple<Middlewares...>>::value;
};
template<typename... Args>
void operator()(crow::request& req, crow::response& res, Args&&... args) const
{
auto& ctx = *reinterpret_cast<typename App::context_t*>(req.middleware_context);
auto& container = *reinterpret_cast<typename App::mw_container_t*>(req.middleware_container);
auto glob_completion_handler = std::move(res.complete_request_handler_);
res.complete_request_handler_ = [] {};
middleware_call_helper<middleware_call_criteria,
0, typename App::context_t, typename App::mw_container_t>(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<typename App::mw_container_t>::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>(args)...);
return std::find(indices_.begin(), indices_.end(), i) != indices_.end();
}
F f;
};
template<typename Route, typename App, typename... Middlewares>
struct handler_call_bridge
{
template<typename MW>
using check_app_contains = typename black_magic::has_type<MW, typename App::mw_container_t>;
static_assert(black_magic::all_true<(std::is_base_of<crow::ILocalMiddleware, Middlewares>::value)...>::value,
"Local middleware has to inherit crow::ILocalMiddleware");
static_assert(black_magic::all_true<(check_app_contains<Middlewares>::value)...>::value,
"Local middleware has to be listed in app middleware");
template<typename F>
void operator()(F&& f) const
{
auto wrapped = handler_middleware_wrapper<F, App, Middlewares...>{std::forward<F>(f)};
tptr->operator()(std::move(wrapped));
}
Route* tptr;
const std::vector<int>& indices_;
};
} // namespace detail

View File

@ -38,14 +38,14 @@ namespace crow
struct context : private partial_context<Middlewares...>
//struct context : private Middlewares::context... // simple but less type-safe
{
template<template<typename QueryMW> 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<typename QueryMW> 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<typename CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N == 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res);
template<typename CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N > 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res);
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
template<typename CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N < std::tuple_size<typename std::remove_reference<Container>::type>::value), bool>::type
middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
middleware_call_helper(const CallCriteria& cc, Container& middlewares, request& req, response& res, Context& ctx);
template<typename T>
typename T::context& get()

View File

@ -7,6 +7,8 @@
#include <memory>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <algorithm>
#include <type_traits>
#include "crow/common.h"
#include "crow/http_response.h"
@ -22,6 +24,53 @@ namespace crow
constexpr const uint16_t INVALID_BP_ID{((uint16_t)-1)};
namespace detail
{
struct middleware_indices
{
template<typename App>
void push()
{}
template<typename App, typename MW, typename... Middlewares>
void push()
{
static_assert(black_magic::has_type<MW, typename App::mw_container_t>::value, "Middleware must be present in app");
indices_.push_back(black_magic::tuple_index<MW, typename App::mw_container_t>::value);
push<App, Middlewares...>();
}
void merge_front(const detail::middleware_indices& idcs)
{
indices_.insert(indices_.begin(), idcs.indices_.cbegin(), idcs.indices_.cend());
}
void merge_back(const detail::middleware_indices& idcs)
{
indices_.insert(indices_.end(), idcs.indices_.cbegin(), idcs.indices_.cend());
}
void pop(const detail::middleware_indices& idcs)
{
for (auto _ : idcs.indices_)
indices_.pop_back();
}
bool empty() const
{
return indices_.empty();
}
void pack()
{
std::sort(indices_.begin(), indices_.end());
indices_.erase(std::unique(indices_.begin(), indices_.end()), indices_.end());
}
std::vector<int> indices_;
};
} // namespace detail
/// A base class for all rules.
///
@ -74,7 +123,6 @@ namespace crow
}
}
std::string custom_templates_base;
const std::string& rule() { return rule_; }
@ -87,6 +135,8 @@ namespace crow
std::unique_ptr<BaseRule> rule_to_upgrade_;
detail::middleware_indices mw_indices_;
friend class Router;
friend class Blueprint;
template<typename T>
@ -474,6 +524,14 @@ namespace crow
static_cast<self_t*>(this)->methods_ |= 1 << static_cast<int>(method);
return static_cast<self_t&>(*this);
}
/// Enable local middleware for this handler
template<typename App, typename... Middlewares>
self_t& middlewares()
{
static_cast<self_t*>(this)->mw_indices_.template push<App, Middlewares...>();
return static_cast<self_t&>(*this);
}
};
/// A rule that can change its parameters during runtime.
@ -607,16 +665,6 @@ namespace crow
detail::routing_handler_call_helper::call_params<decltype(handler_)>{handler_, params, req, res});
}
/// Enable local middleware for this handler
template<typename App, typename... Middlewares>
crow::detail::handler_call_bridge<TaggedRule<Args...>, 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<void(crow::request&, crow::response&, Args...)> handler_;
};
@ -1128,6 +1176,12 @@ namespace crow
return catchall_rule_;
}
template<typename App, typename... Middlewares>
void middlewares()
{
mw_indices_.push<App, Middlewares...>();
}
private:
void apply_blueprint(Blueprint& blueprint)
{
@ -1153,6 +1207,7 @@ namespace crow
std::vector<std::unique_ptr<BaseRule>> all_rules_;
CatchallRule catchall_rule_;
std::vector<Blueprint*> blueprints_;
detail::middleware_indices mw_indices_;
friend class Router;
};
@ -1199,6 +1254,8 @@ namespace crow
rule_without_trailing_slash.pop_back();
}
ruleObject->mw_indices_.pack();
ruleObject->foreach_method([&](int method) {
per_methods_[method].rules.emplace_back(ruleObject);
per_methods_[method].trie.add(rule, per_methods_[method].rules.size() - 1, BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, BP_index);
@ -1244,7 +1301,7 @@ namespace crow
}
}
void validate_bp(std::vector<Blueprint*> blueprints)
void validate_bp(std::vector<Blueprint*> blueprints, detail::middleware_indices& current_mw)
{
for (unsigned i = 0; i < blueprints.size(); i++)
{
@ -1259,6 +1316,8 @@ namespace crow
per_methods_[i].trie.add(blueprint->prefix(), 0, blueprint->prefix().length(), i);
}
}
current_mw.merge_back(blueprint->mw_indices_);
for (auto& rule : blueprint->all_rules_)
{
if (rule)
@ -1267,17 +1326,20 @@ namespace crow
if (upgraded)
rule = std::move(upgraded);
rule->validate();
rule->mw_indices_.merge_front(current_mw);
internal_add_rule_object(rule->rule(), rule.get(), i, blueprints);
}
}
validate_bp(blueprint->blueprints_);
validate_bp(blueprint->blueprints_, current_mw);
current_mw.pop(blueprint->mw_indices_);
}
}
void validate()
{
//Take all the routes from the registered blueprints and add them to `all_rules_` to be processed.
validate_bp(blueprints_);
detail::middleware_indices blueprint_mw;
validate_bp(blueprints_, blueprint_mw);
for (auto& rule : all_rules_)
{
@ -1442,6 +1504,7 @@ namespace crow
return std::string();
}
template<typename App>
void handle(request& req, response& res)
{
HTTPMethod method_actual = req.method;
@ -1554,7 +1617,8 @@ namespace crow
// any uncaught exceptions become 500s
try
{
rules[rule_index]->handle(req, res, std::get<2>(found));
auto& rule = rules[rule_index];
handle_rule<App>(rule, req, res, std::get<2>(found));
}
catch (std::exception& e)
{
@ -1572,6 +1636,47 @@ namespace crow
}
}
template<typename App>
typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value != 0, void>::type
handle_rule(BaseRule* rule, crow::request& req, crow::response& res, const crow::routing_params& rp)
{
if (!rule->mw_indices_.empty())
{
auto& ctx = *reinterpret_cast<typename App::context_t*>(req.middleware_context);
auto& container = *reinterpret_cast<typename App::mw_container_t*>(req.middleware_container);
detail::middleware_call_criteria_dynamic crit{rule->mw_indices_.indices_};
auto glob_completion_handler = std::move(res.complete_request_handler_);
res.complete_request_handler_ = [] {};
detail::middleware_call_helper<detail::middleware_call_criteria_dynamic,
0, typename App::context_t, typename App::mw_container_t>(crit, container, req, res, ctx);
if (res.completed_)
{
glob_completion_handler();
return;
}
res.complete_request_handler_ = [&rule, &crit, &ctx, &container, &req, &res, &glob_completion_handler] {
detail::after_handlers_call_helper<
detail::middleware_call_criteria_dynamic,
std::tuple_size<typename App::mw_container_t>::value - 1,
typename App::context_t,
typename App::mw_container_t>(crit, container, ctx, req, res);
glob_completion_handler();
};
}
rule->handle(req, res, rp);
}
template<typename App>
typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value == 0, void>::type
handle_rule(BaseRule* rule, crow::request& req, crow::response& res, const crow::routing_params& rp)
{
rule->handle(req, res, rp);
}
void debug_print()
{
for (int i = 0; i < static_cast<int>(HTTPMethod::InternalMethodCount); i++)

View File

@ -263,6 +263,22 @@ namespace crow
struct has_type<T, std::tuple<T, Ts...>> : std::true_type
{};
// Find index of type in tuple
template<class T, class Tuple>
struct tuple_index;
template<class T, class... Types>
struct tuple_index<T, std::tuple<T, Types...>>
{
static const int value = 0;
};
template<class T, class U, class... Types>
struct tuple_index<T, std::tuple<U, Types...>>
{
static const int value = 1 + tuple_index<T, std::tuple<Types...>>::value;
};
// Check F is callable with Args
template<typename F, typename... Args>
struct is_callable

View File

@ -8,6 +8,7 @@
#include <vector>
#include <thread>
#include <chrono>
#include <type_traits>
#include "catch.hpp"
#include "crow.h"
@ -1253,7 +1254,11 @@ struct IntSettingMiddleware
std::vector<std::string> test_middleware_context_vector;
struct FirstMW
struct empty_type
{};
template<bool Local>
struct FirstMW : public std::conditional<Local, crow::ILocalMiddleware, empty_type>::type
{
struct context
{
@ -1272,38 +1277,40 @@ struct FirstMW
}
};
struct SecondMW
template<bool Local>
struct SecondMW : public std::conditional<Local, crow::ILocalMiddleware, empty_type>::type
{
struct context
{};
template<typename AllContext>
void before_handle(request& req, response& res, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("2 before");
all_ctx.template get<FirstMW<Local>>().v.push_back("2 before");
if (req.url == "/break") res.end();
}
template<typename AllContext>
void after_handle(request&, response&, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("2 after");
all_ctx.template get<FirstMW<Local>>().v.push_back("2 after");
}
};
struct ThirdMW
template<bool Local>
struct ThirdMW : public std::conditional<Local, crow::ILocalMiddleware, empty_type>::type
{
struct context
{};
template<typename AllContext>
void before_handle(request&, response&, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("3 before");
all_ctx.template get<FirstMW<Local>>().v.push_back("3 before");
}
template<typename AllContext>
void after_handle(request&, response&, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("3 after");
all_ctx.template get<FirstMW<Local>>().v.push_back("3 after");
}
};
@ -1316,7 +1323,7 @@ TEST_CASE("middleware_context")
// or change the order of FirstMW and SecondMW
// App<IntSettingMiddleware, SecondMW, FirstMW> app;
App<IntSettingMiddleware, FirstMW, SecondMW, ThirdMW> app;
App<IntSettingMiddleware, FirstMW<false>, SecondMW<false>, ThirdMW<false>> app;
int x{};
CROW_ROUTE(app, "/")
@ -1326,7 +1333,7 @@ TEST_CASE("middleware_context")
x = ctx.val;
}
{
auto& ctx = app.get_context<FirstMW>(req);
auto& ctx = app.get_context<FirstMW<false>>(req);
ctx.v.push_back("handle");
}
@ -1335,7 +1342,7 @@ TEST_CASE("middleware_context")
CROW_ROUTE(app, "/break")
([&](const request& req) {
{
auto& ctx = app.get_context<FirstMW>(req);
auto& ctx = app.get_context<FirstMW<false>>(req);
ctx.v.push_back("handle");
}
@ -1452,6 +1459,59 @@ TEST_CASE("local_middleware")
app.stop();
} // local_middleware
TEST_CASE("middleware_blueprint")
{
static char buf[2048];
App<FirstMW<true>, SecondMW<true>, ThirdMW<true>> app;
Blueprint bp1("a", "c1", "c1");
bp1.CROW_MIDDLEWARES(app, FirstMW<true>);
Blueprint bp2("b", "c2", "c2");
bp2.CROW_MIDDLEWARES(app, SecondMW<true>);
CROW_BP_ROUTE(bp2, "/")
.CROW_MIDDLEWARES(app, ThirdMW<true>)([&app](const crow::request& req) {
{
auto& ctx = app.get_context<FirstMW<true>>(req);
ctx.v.push_back("handle");
}
return "";
});
bp1.register_blueprint(bp2);
app.register_blueprint(bp1);
auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(45451).run_async();
app.wait_for_server_start();
std::string sendmsg = "GET /a/b/\r\n\r\n";
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(sendmsg));
c.receive(asio::buffer(buf, 2048));
c.close();
}
{
auto& out = test_middleware_context_vector;
CHECK(7 == out.size());
CHECK("1 before" == out[0]);
CHECK("2 before" == out[1]);
CHECK("3 before" == out[2]);
CHECK("handle" == out[3]);
CHECK("3 after" == out[4]);
CHECK("2 after" == out[5]);
CHECK("1 after" == out[6]);
}
app.stop();
} // middleware_blueprint
TEST_CASE("middleware_cookieparser")
{
static char buf[2048];