-
Notifications
You must be signed in to change notification settings - Fork 888
Middleware
Many middlewares may depend on each other. For example just imagine a session mananger without cookies... It doesn't sound right, does it? Then we need shared contexts in order to let the session middleware get access to the given cookies.
There always many streets to rome but always the same problem. Since crow makes heavy usage of templates its almost impossible to make each middleware know each other without eighter rewriting the current (oct 2017) middleware api or running into an chicken-egg issue.
Thats why using lambda is an easy solution. We just store some (one!) template related operation and use it later on.
struct Sessions {
/** our lambda function **/
std::function<CookieParser::context& (crow::request& req)> lamGetCookieCtx;
/** stores an enviroment that allows us to access the cookies context **/
template<typename ... Middlewares>
void init(crow::Crow<Middlewares...>& app) {
lamGetCookieCtx = [&](crow::request& req) -> CookieParser::context& {
CROW_LOG_INFO << "GET COOKIE CONTEXT";
return app.template get_context<CookieParser>(req);
};
}
struct context
{
// whatever you need on your session
};
void before_handle(crow::request& req, crow::response& res, context& ctx)
{
auto& cookiectx = lamGetCookieCtx(req);
// do stuff ...
}
void after_handle(crow::request&, crow::response& res, context& ctx)
{
}
};
The App setup might looks like this:
crow::App<CookieParser, Sessions> app;
app.get_middleware<Sessions>().init(app);
This is it. With just 5 Lines of code you made an random Middleware available. The big pro on this solution is, that its just few lines of code. On the negative side you need to take, that you need to call init() once you got an main crow instance set. Also you need to repeat the same code again and again for each middleware you want to use.
Writing an middleware isnt as difficult. Having an look into the CookieParser and the Examples might allready tell you the whole deal.
Lets have a look at the absolute minimum of an Middleware skeleton:
/** call it whatever u want, but do not use templates. it wont work **/
struct Skeletor {
// skeletors member variables go here
Skeletor() {
// dont forget to init the members!
}
struct context {
// clients very private context.
// everything from variables to methods goes here
}
/** use the namespace to avoid problems **/
void before_handle(crow::request& req, crow::response& res, context& ctx) {
// code that will run before the routing takes over
}
void after_handle(crow::request& /*req*/, crow::response& res, context& ctx) {
// code that will run aafter the routing takes over
}
};
That is it. There only few things to keep in mind when writing an Middleware.
- Never forget that the "Middleware" and the "Context" are two different areas. First brings the global functionality of the middleware any maybe, maybe some additional code that inits external libs, cross contexts and so one. But not more. Second contains the "whole world" of your client. 90% of the code should be here.
- The global lifecycle is always before_handle, then whatever you do inside of the routing, then after_handle. If you want to bring infos to the user make sure to store it before his code is running. If you need to store something or clean up do it after the routing code was running.