From 845aeaac50b471c4bf4fffac9407c586a786cb59 Mon Sep 17 00:00:00 2001 From: Nitromelon Date: Sat, 11 May 2024 14:18:19 +0800 Subject: [PATCH] Add document for middleware. (#69) --- CHN-05-中间件和过滤器.md | 101 +++++++++++++++++++++++++++++++ CHN-05-过滤器.md | 59 ------------------ ENG-05-Filter.md | 58 ------------------ ENG-05-Middleware-and-Filter.md | 103 ++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 117 deletions(-) create mode 100644 CHN-05-中间件和过滤器.md delete mode 100644 CHN-05-过滤器.md delete mode 100644 ENG-05-Filter.md create mode 100644 ENG-05-Middleware-and-Filter.md diff --git a/CHN-05-中间件和过滤器.md b/CHN-05-中间件和过滤器.md new file mode 100644 index 0000000..5b295e3 --- /dev/null +++ b/CHN-05-中间件和过滤器.md @@ -0,0 +1,101 @@ +[English](ENG-05-Middleware-and-Filter) | [简体中文](CHN-05-中间件和过滤器) + +中间件(middleware)和过滤器(filter)可以帮助用户提高编程效率,在HttpController的[例子](CHN-04-2-控制器-HttpController)中,getInfo方法在返回用户信息之前应该先校验用户是否登录,我们把这个逻辑写在getInfo方法里当然是可以的,但是,很显然,校验用户登录属于通用逻辑,很多接口都将用到,应该把它单独提取出来,再配置到调用handler之前,这就是filter的作用。 + +drogon的中间件采用了洋葱圈模型, 框架做完URL路径匹配后,会依次调用注册到该路径上的中间件,在每个中间件中,用户可以选择拦截或放行请求,并添加前置、后置处理逻辑。 +如果有一个中间件拦截了请求,该请求将不会继续深入洋葱圈内层,对应的handler也不会被调用,但是仍然会通过外层中间件的后置处理逻辑。 + +过滤器实际上是省略后置操作的中间件,经过进一步的包装后暴露给用户。中间件和过滤器可以在注册路径时混合使用。 + +### 内置中间件/过滤器 + +drogon内置了如下常用过滤器: + +* `drogon::IntranetIpFilter`:只放行内网ip发来的http请求,否则返回404页面; +* `drogon::LocalHostFilter`:只放行本机127.0.0.1或者::1发来的http请求,否则返回404页面; + +### 自定义中间件/过滤器 + +* #### 中间件的定义 + 用户可以自定义中间件,需要继承`HttpMiddleware`类模板,模板类型就是子类类型,比如我们想为某些路由开启跨域支持,就可以定义如下: + ```c++ + class MyMiddleware : public HttpMiddleware + { + public: + MyMiddleware(){}; // do not omit constructor + + void invoke(const HttpRequestPtr &req, + MiddlewareNextCallback &&nextCb, + MiddlewareCallback &&mcb) override + { + const std::string &origin = req->getHeader("origin"); + if (origin.find("www.some-evil-place.com") != std::string::npos) + { + // intercept directly + mcb(HttpResponse::newNotFoundResponse(req)); + return; + } + // Do something before calling the next middleware + nextCb([mcb = std::move(mcb)](const HttpResponsePtr &resp) { + // Do something after the next middleware returns + resp->addHeader("Access-Control-Allow-Origin", origin); + resp->addHeader("Access-Control-Allow-Credentials","true"); + mcb(resp); + }); + } + }; + ``` + + 我们需要重载父类的`invoke`虚函数实现中间件逻辑; + + 这个虚函数有三个参数,分别是: + + * **req**: http请求; + * **nextCb**:进入内层的回调函数,调用该函数意味着继续深入洋葱圈内层,调用下一个中间件或最终handler。 + 调用nextCb时接受另一个函数作为参数, 当从洋葱圈内层返回时,该函数会被调用,并传入内层返回的HttpResponsePtr。 + * **mcb**:返回上层的回调函数,调用该函数意味着返回洋葱圈上层。若跳过nextCb只调用mcb,意味着拦截该请求,直接返回上层。 + +* #### 过滤器的定义 + + 用户可以自定义过滤器,需要继承HttpFilter类模板,模板类型就是子类类型,比如我们想做一个LoginFilter,就可以定义如下: + + ```c++ + class LoginFilter:public drogon::HttpFilter + { + public: + void doFilter(const HttpRequestPtr &req, + FilterCallback &&fcb, + FilterChainCallback &&fccb) override ; + }; + ``` + + 你可以通过 `drogon_ctl` 命令创建过滤器, 见 [drogon_ctl](CHN-11-drogon_ctl命令#过滤器创建). + + 我们需要重载父类的doFilter虚函数实现过滤器逻辑; + + 这个虚函数有三个参数,分别是: + + * **req**: http请求; + * **fcb**:过滤器回调函数,函数类型是void (HttpResponsePtr),当过滤器判定请求不合法时,通过这个回调把特定的响应返回给浏览器; + * **fccb**:过滤器链回调函数,函数类型是void(),当过滤器判定请求合法时,通过这个回调告诉drogon调用下一个过滤器或者最终的handler; + + 具体的实现可以参考drogon内置过滤器的实现。 + +* #### 中间件/过滤器的注册 + + 中间件/过滤器总是伴随controller的注册进行,前面提到的注册handler的宏(PATH_ADD,METHOD_ADD等)都可以在最后添加一个或多个中间件/过滤器名字;比如,我们把前面getInfo方法的注册行改为如下形式: + + ```c++ + METHOD_ADD(User::getInfo,"/{1}/info?token={2}",Get,"LoginFilter","MyMiddleware"); + ``` + + 则在路径匹配成功后,必须满足如下条件,getInfo方法才会被调用: + + 1. 请求必须是http get请求; + 2. 请求方必须已经登录; + + 可以看到,中间件/过滤器的配置和注册是非常简单的,注册中间件的controller文件并不需要引用中间件的头文件,中间件和控制器也是充分解耦的。 + + > **注意: 如果中间件/过滤器定义在命名空间里,注册时必须把命名空间写全** + +# 06 [视图](CHN-06-视图) diff --git a/CHN-05-过滤器.md b/CHN-05-过滤器.md deleted file mode 100644 index d8abf9b..0000000 --- a/CHN-05-过滤器.md +++ /dev/null @@ -1,59 +0,0 @@ -[English](ENG-05-Filter) | [简体中文](CHN-05-过滤器) - -过滤器(filter)可以帮助用户提高编程效率,在HttpController的[例子](CHN-04-2-控制器-HttpController)中,getInfo方法在返回用户信息之前应该先校验用户是否登录,我们把这个逻辑写在getInfo方法里当然是可以的,但是,很显然,校验用户登录属于通用逻辑,很多接口都将用到,应该把它单独提取出来,再配置到调用handler之前,这就是filter的作用。 - -drogon框架做完URL路径匹配后,会先依次调用注册到该路径上的过滤器,只有当所有过滤器都允许"通过"时,对应的handler才会被调用; - -### 内置过滤器 - -drogon内置了如下常用过滤器: - -* `drogon::IntranetIpFilter`:只放行内网ip发来的http请求,否则返回404页面; -* `drogon::LocalHostFilter`:只放行本机127.0.0.1或者::1发来的http请求,否则返回404页面; - -### 自定义过滤器 - -* #### 过滤器的定义 - - 当然,用户可以自定义过滤器,需要继承HttpFilter类模板,模板类型就是子类类型,比如我们想做一个LoginFilter,就可以定义如下: - - ```c++ - class LoginFilter:public drogon::HttpFilter - { - public: - virtual void doFilter(const HttpRequestPtr &req, - FilterCallback &&fcb, - FilterChainCallback &&fccb) override ; - }; - ``` - - 你可以通过 `drogon_ctl` 命令创建过滤器, 见 [drogon_ctl](CHN-11-drogon_ctl命令#过滤器创建). - - 我们需要重载父类的doFilter虚函数实现过滤器逻辑; - - 这个虚函数有三个参数,分别是: - - * **req**: http请求; - * **fcb**:过滤器回调函数,函数类型是void (HttpResponsePtr),当过滤器判定请求不合法时,通过这个回调把特定的响应返回给浏览器; - * **fccb**:过滤器链回调函数,函数类型是void(),当过滤器判定请求合法时,通过这个回调告诉drogon调用下一个过滤器或者最终的handler; - - 具体的实现可以参考drogon内置过滤器的实现。 - -* #### 过滤器的注册 - - 过滤器总是伴随controller的注册进行,前面提到的注册handler的宏(PATH_ADD,METHOD_ADD等)都可以在最后添加一个或多个过滤器名字;比如,我们把前面getInfo方法的注册行改为如下形式: - - ```c++ - METHOD_ADD(User::getInfo,"/{1}/info?token={2}",Get,"LoginFilter"); - ``` - - 则在路径匹配成功后,必须满足如下条件,getInfo方法才会被调用: - - 1. 请求必须是http get请求; - 2. 请求方必须已经登录; - - 可以看到,过滤器的配置和注册是非常简单的,注册过滤器的controller文件并不需要引用过滤器的头文件,过滤器和控制器也是充分解耦的。 - - > **注意: 如果过滤器定义在命名空间里,注册过滤器时必须把命名空间写全** - -# 06 [视图](CHN-06-视图) diff --git a/ENG-05-Filter.md b/ENG-05-Filter.md deleted file mode 100644 index 8bd9bbd..0000000 --- a/ENG-05-Filter.md +++ /dev/null @@ -1,58 +0,0 @@ -[English](ENG-05-Filter) | [简体中文](CHN-05-过滤器) - -In HttpController's [example](ENG-04-2-Controller-HttpController), the getInfo method should check whether the user is logged in before returning the user's information. We can write this logic in the getInfo method, but obviously, checking the user's login membership is general logic which will be used by many interfaces, it should be extracted separately and configured before calling handler, which is what filters do. -After the drogon framework completes the URL path matching, it first calls the filters registered on the path in turn, and only when all the filters allow "pass", the corresponding handler will be called; - -### Built-in Filter - -Drogon contains the following common filters: - -- `drogon::IntranetIpFilter`: allow HTTP requests from intranet IP only, or return the 404 page. -- `drogon::LocalHostFilter`: allow HTTP requests from 127.0.0.1 or ::1 only, or return the 404 page. - -### Custom Filter - -- #### Filter Definition - - Of course, users can customize the filter, you need to inherit the HttpFilter class template, the template type is the subclass type, for example, if you want to create a LoginFilter, you could define it as follows: - - ```c++ - class LoginFilter:public drogon::HttpFilter - { - public: - virtual void doFilter(const HttpRequestPtr &req, - FilterCallback &&fcb, - FilterChainCallback &&fccb) override ; - }; - ``` - - You could create filter by the `drogon_ctl` command, see [drogon_ctl](ENG-11-drogon_ctl-command#Filter-creation). - - You need to override the doFilter virtual function of the parent class to implement the filter logic; - - This virtual function has three parameters, which are: - - - **req**: http request; - - **fcb**: filter callback function, the function type is void (HttpResponsePtr), when the filter determines that the request is not valid, the specific response is returned to the browser through this callback; - - **fccb**: filter chain callback function, the function type is void (), when the filter determines that the request is legal, tells drogon to call the next filter or the final handler through this callback; - - The specific implementation can refer to the implementation of the drogon built-in filter. - -- #### Filter Registration - - The registration of filters is always accompanied by the registration of controllers.the macros (PATH_ADD, METHOD_ADD, etc.) mentioned earlier can add the name of one or more filters at the end; for example, we change the registration line of the previous `getInfo` method to the following form: - - ```c++ - METHOD_ADD(User::getInfo,"/{1}/info?token={2}",Get,"LoginFilter"); - ``` - - After the path is successfully matched, the `getInfo` method will be called only when the following conditions were met: - - 1. The request must be an HTTP Get request; - 2. The requesting party must have logged in; - - As you can see, the configuration and registration of filters are very simple. The controller source file that registers filters does not need to include the filter's header file. The filter and controller are fully decoupled. - - > **Note: If the filter is defined in the namespace, you must write the namespace completely when you register the filter.** - -# 06 [View](ENG-06-View) diff --git a/ENG-05-Middleware-and-Filter.md b/ENG-05-Middleware-and-Filter.md new file mode 100644 index 0000000..2bfa81b --- /dev/null +++ b/ENG-05-Middleware-and-Filter.md @@ -0,0 +1,103 @@ +[English](ENG-05-Middleware-and-Filter) | [简体中文](CHN-05-中间件和过滤器) + +In HttpController's [example](ENG-04-2-Controller-HttpController), the getInfo method should check whether the user is logged in before returning the user's information. We can write this logic in the getInfo method, but obviously, checking the user's login membership is general logic which will be used by many interfaces, it should be extracted separately and configured before calling handler, which is what filters do. + +Drogon's middleware uses the onion model. After the framework completes URL path matching, it sequentially invokes the middleware registered for that path. Within each middleware, users can choose to intercept or pass through the request and add pre-processing and post-processing logic. + +If a middleware intercepts a request, it will not continue to the inner layers of the onion, and the corresponding handler will not be invoked. However, it will still go through the post-processing logic of the outer layer middleware. + +Filters, in fact, are middleware that omit the post-processing operation. Middleware and filters can be used in combination when registering paths. + +### Built-in Middleware/Filter + +Drogon contains the following common filters: + +- `drogon::IntranetIpFilter`: allow HTTP requests from intranet IP only, or return the 404 page. +- `drogon::LocalHostFilter`: allow HTTP requests from 127.0.0.1 or ::1 only, or return the 404 page. + +### Custom Middleware/Filter + +- #### Middleware Definition + + Users can customize the middleware, you need to inherit the `HttpMiddleware` class template, the template type is the subclass type, for example, if you want to enable cross-region support for come routes, you could define it as follows: + + ```c++ + class MyMiddleware : public HttpMiddleware + { + public: + MyMiddleware(){}; // do not omit constructor + + void invoke(const HttpRequestPtr &req, + MiddlewareNextCallback &&nextCb, + MiddlewareCallback &&mcb) override + { + const std::string &origin = req->getHeader("origin"); + if (origin.find("www.some-evil-place.com") != std::string::npos) + { + // intercept directly + mcb(HttpResponse::newNotFoundResponse(req)); + return; + } + // Do something before calling the next middleware + nextCb([mcb = std::move(mcb)](const HttpResponsePtr &resp) { + // Do something after the next middleware returns + resp->addHeader("Access-Control-Allow-Origin", origin); + resp->addHeader("Access-Control-Allow-Credentials","true"); + mcb(resp); + }); + } + }; + ``` + + You need to override the `invoke` virtual function of the parent class to implement the filter logic; + + This virtual function has three parameters, which are: + + - **req**: http request; + * **nextCb**:The callback function to enter the inner layer of the onion. Calling this function means invoking the next middleware or the final handler. When calling nextCb, it accepts another function as a parameter. This function will be called when returning from the inner layers, and the HttpResponsePtr returned from the inner layers will be passed as an argument to this function. + * **mcb**:The callback function to return to the upper layer of the onion. Calling this function means returning to the outer layer of the onion. If nextCb is skipped and only mcb is called, it means intercepting the request and directly returning to the upper layer. + +- #### Filter Definition + + Of course, users can customize the filter, you need to inherit the HttpFilter class template, the template type is the subclass type, for example, if you want to create a LoginFilter, you could define it as follows: + + ```c++ + class LoginFilter:public drogon::HttpFilter + { + public: + void doFilter(const HttpRequestPtr &req, + FilterCallback &&fcb, + FilterChainCallback &&fccb) override ; + }; + ``` + + You could create filter by the `drogon_ctl` command, see [drogon_ctl](ENG-11-drogon_ctl-command#Filter-creation). + + You need to override the doFilter virtual function of the parent class to implement the filter logic; + + This virtual function has three parameters, which are: + + - **req**: http request; + - **fcb**: filter callback function, the function type is void (HttpResponsePtr), when the filter determines that the request is not valid, the specific response is returned to the browser through this callback; + - **fccb**: filter chain callback function, the function type is void (), when the filter determines that the request is legal, tells drogon to call the next filter or the final handler through this callback; + + The specific implementation can refer to the implementation of the drogon built-in filter. + +- #### Middleware/Filter Registration + + The registration of middlewares/filters is always accompanied by the registration of controllers.the macros (PATH_ADD, METHOD_ADD, etc.) mentioned earlier can add the name of one or more middlewares/filters at the end; for example, we change the registration line of the previous `getInfo` method to the following form: + + ```c++ + METHOD_ADD(User::getInfo,"/{1}/info?token={2}",Get,"LoginFilter","MyMiddleware"); + ``` + + After the path is successfully matched, the `getInfo` method will be called only when the following conditions were met: + + 1. The request must be an HTTP Get request; + 2. The requesting party must have logged in; + + As you can see, the configuration and registration of middlewares/filters are very simple. The controller source file that registers middlewares does not need to include the middlewares's header file. The middleware and controller are fully decoupled. + + > **Note: If the middleware/filter is defined in the namespace, you must write the namespace completely when you register it.** + +# 06 [View](ENG-06-View)