Skip to content

Commit

Permalink
Merge pull request ipkn#403 from dranikpg/blueprint-middleware
Browse files Browse the repository at this point in the history
Blueprint middleware
  • Loading branch information
The-EDev committed May 23, 2022
2 parents c897101 + a6bf90f commit a36d5c3
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 167 deletions.
97 changes: 48 additions & 49 deletions docs/guides/middleware.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,78 @@
Middleware is used for altering and inspecting requests before and after the handler call.
Middleware is used for altering and inspecting requests before and after calling the handler. In Crow it's very similar to middleware in other web frameworks.

All middleware is registered in the Crow application

```cpp
crow::App<FirstMW, SecondMW, ThirdMW> app;
```

and is called in this specified order.

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 context struct for storing request local data.
* A `before_handle` method, which is called before the handler.
* A `after_handle` method, which is called after the handler.

## before_handle
There are two possible signatures for before_handle
!!! warning

1. if you only need to access this middleware's context.
As soon as `response.end()` is called, no other handlers and middleware is run, except for after_handlers of already visited middleware.

```cpp
void before_handle(request& req, response& res, context& ctx)
```

2. To get access to other middlewares context
## Example

``` cpp
template <typename AllContext>
void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
A middleware that can be used to guard admin handlers

```cpp
struct AdminAreaGuard
{
struct context
{};

void before_handle(crow::request& req, crow::response& res, context& ctx)
{
auto other_ctx = all_ctx.template get<OtherMiddleware>();
if (req.remote_ip_address != ADMIN_IP)
{
res.code = 403;
res.end();
}
}
```

void after_handle(crow::request& req, crow::response& res, context& ctx)
{}
};
```


## after_handle
There are two possible signatures for after_handle
### before_handle and after_handle
There are two possible signatures for before_handle and after_handle

1. if you only need to access this middleware's context.

```cpp
void after_handle(request& req, response& res, context& ctx)
void before_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 before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
{
auto other_ctx = all_ctx.template get<OtherMiddleware>();
}
```

## Using middleware
## Local middleware

All middleware has to be registered in the Crow application and is enabled globally by default.

```cpp
crow::App<FirstMiddleware, SecondMiddleware> app;
```

if you want to enable some middleware only for specific handlers, you have to extend it from `crow::ILocalMiddleware`.
By default, every middleware is called for each request. If you want to enable middleware for specific handlers or blueprints, you have to extend it from `crow::ILocalMiddleware`

```cpp
struct LocalMiddleware : crow::ILocalMiddleware
{
...
```
After this, you can enable it for specific handlers.
After this, you can enable it for specific handlers
```cpp
CROW_ROUTE(app, "/with_middleware")
Expand All @@ -71,26 +82,14 @@ CROW_ROUTE(app, "/with_middleware")
});
```

## Examples

A local middleware that can be used to guard admin handlers
or blueprints

```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)
{}
};
Blueprint bp("with_middleware");
bp.CROW_MIDDLEWARES(app, FistLocalMiddleware, SecondLocalMiddleware);
```
!!! warning
Local and global middleware are called separately. First all global middleware is run, then all enabled local middleware for the current handler is run. In both cases middleware is called strongly
in the order listed in the Crow application.
38 changes: 36 additions & 2 deletions examples/example_middleware.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ struct RequestLogger
struct context
{};

// This method is run before handling the request
void before_handle(crow::request& req, crow::response& /*res*/, context& /*ctx*/)
{
CROW_LOG_INFO << "Request to:" + req.url;
}

// This method is run after handling the request
void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}
};
Expand All @@ -23,6 +25,7 @@ struct SecretContentGuard : crow::ILocalMiddleware

void before_handle(crow::request& /*req*/, crow::response& res, context& /*ctx*/)
{
// A request can be aborted prematurely
res.write("SECRET!");
res.code = 403;
res.end();
Expand All @@ -32,10 +35,28 @@ struct SecretContentGuard : crow::ILocalMiddleware
{}
};

struct RequestAppend : crow::ILocalMiddleware
{
// Values from this context can be accessed from handlers
struct context
{
std::string message;
};

void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}

void after_handle(crow::request& /*req*/, crow::response& res, context& ctx)
{
// The response can be modified
res.write(" + (" + ctx.message + ")");
}
};

int main()
{
// ALL middleware (including per handler) is listed
crow::App<RequestLogger, SecretContentGuard> app;
crow::App<RequestLogger, SecretContentGuard, RequestAppend> app;

CROW_ROUTE(app, "/")
([]() {
Expand All @@ -48,7 +69,20 @@ int main()
return "";
});

app.port(18080).run();
crow::Blueprint bp("bp", "c", "c");
// Register middleware on all routes on a specific blueprint
// This also applies to sub blueprints
bp.CROW_MIDDLEWARES(app, RequestAppend);

CROW_BP_ROUTE(bp, "/")
([&](const crow::request& req) {
// Get RequestAppends context
auto& ctx = app.get_context<RequestAppend>(req);
ctx.message = "World";
return "Hello:";
});
app.register_blueprint(bp);

app.port(18080).run();
return 0;
}
2 changes: 1 addition & 1 deletion include/crow/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,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**)
Expand Down
4 changes: 2 additions & 2 deletions include/crow/http_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,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_)
{
Expand Down Expand Up @@ -198,7 +198,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())
Expand Down
9 changes: 2 additions & 7 deletions include/crow/http_response.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,15 @@ 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
{
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.
Expand Down
Loading

0 comments on commit a36d5c3

Please sign in to comment.