Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local middleware #327

Merged
merged 11 commits into from
Feb 14, 2022
97 changes: 81 additions & 16 deletions docs/guides/middleware.md
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)
{}
};
```
4 changes: 4 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions examples/example_middleware.cpp
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;
}
1 change: 1 addition & 0 deletions include/crow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion include/crow/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#else
#define CROW_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url)
#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged<crow::black_magic::get_parameter_tag(url)>(url)
#define CROW_MIDDLEWARES(app, ...) middlewares<decltype(app), __VA_ARGS__>()
#endif
#define CROW_CATCHALL_ROUTE(app) app.catchall_route()
#define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule()
Expand Down Expand Up @@ -68,7 +69,7 @@ namespace crow
}

/// Process the request and generate a response for it
void handle(const request& req, response& res)
Copy link
Member Author

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

void handle(request& req, response& res)
{
router_.handle(req, res);
}
Expand Down Expand Up @@ -394,6 +395,7 @@ namespace crow

// middleware
using context_t = detail::context<Middlewares...>;
using mw_container_t = std::tuple<Middlewares...>;
template<typename T>
typename T::context& get_context(const request& req)
{
Expand Down
151 changes: 6 additions & 145 deletions include/crow/http_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -23,150 +24,6 @@ namespace crow
using namespace boost;
using tcp = asio::ip::tcp;

namespace detail
Copy link
Member Author

Choose a reason for hiding this comment

The 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;
Expand Down Expand Up @@ -316,8 +173,11 @@ namespace crow

ctx_ = detail::context<Middlewares...>();
req.middleware_context = static_cast<void*>(&ctx_);
req.middleware_container = static_cast<void*>(middlewares_);
Copy link
Member Author

Choose a reason for hiding this comment

The 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_)
{
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions include/crow/http_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
Loading