【C++/Drogon框架】二、控制器(controller)、过滤器(filter)与视图(view)

【C++/Drogon框架】二、控制器(controller)、过滤器(filter)与视图(view)

一、 控制器(controller)

控制器是Web开发中重要的部分,其可以接收浏览器发来的请求并生成相应的数据进行响应,一般用来为前端提供接口,或结合视图直接向用户提供功能接口。在Drogon中,有关网络传输、http解析等并不需要我们关心,我们要做的只有实现逻辑功能。

在controller中,我们可以定义多个函数(一般叫做handler),用来处理一个模块中的多个逻辑功能,如用户的登录和注册。

Drogon的controller一共有三种:HttpSimpleControllerHttpControllerWebSocketController,使用时只要继承响应的类模板即可。

1、HttpSimpleController

我们可以使用drogon_ctl工具快捷生成一个名为testController的控制器:

drogon_ctl create controller TestController
#注意:如果你是用的drogon_ctl创建的项目,这条命令要在controller目录下执行
  • 每个HttpSimpleController只能有一个handler,而且通过虚函数定义

  • Drogon为我们提供了一个宏PATH_ADD用来进行路径映射,它要被包含在PATH_LIST_BEGINPATH_LIST_END之间,这个宏的第一个参数为你要映射的路径,然后它还提供两种约束:一种是HttpMethod的类型,用来规定请求方式,另一种是HttpFilter,用来过滤请求,详见

下面举一个例子:

TestController.h

#pragma once

#include <drogon/HttpSimpleController.h>

using namespace drogon;

class TestController : public drogon::HttpSimpleController<testController>
{
  public:
    // 只能有一个handler且用虚函数定义
    void asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback) override;
    PATH_LIST_BEGIN
        // list path definitions here;
        // PATH_ADD("/path", "filter1", "filter2", HttpMethod1, HttpMethod2...);
    // 为根路径的POST请求进行映射
    PATH_ADD("/", Post);
    // 为/test路径的Get请求进行映射
    PATH_ADD("/test", Get);
    PATH_LIST_END
};

TestController.cc

#include "TestController.h"

void TestController::asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback)
{
    // write your application logic here
    // 创建Http响应
    auto resp = HttpResponse::newHttpResponse();
    // 设置状态码为200
    resp->setStatusCode(k200OK);
    // 设置contentType
    resp->setContentTypeCode(CT_TEXT_HTML);
    // 设置响应体
    if (req->getMethod() == Get) {
        resp->setBody("Get!");
    }
    else if (req->getMethod() == Post) {
        resp->setBody("Post!");
    }
    // 调用回调函数
    callback(resp);
}

结果如下:

在这里插入图片描述
在这里插入图片描述

2、HttpController

HttpController也可以通过drogon_ctl快速生成,只需要加伤-h参数

drogon_ctl create controller -h HttpTestController
  • HttpController中drogon为我们提供了两种添加映射的宏:METHOD_ADDADD_METHOD_TO,其中,

    • METHOD_ADD添加的映射会自动添加前缀(命名空间+类名),比如在命名空间demo下的类HttpTestController使用该宏添加映射会自动增加/demo/HttpTestController/的前缀;

    • ADD_METHOD_TO宏则不会添加前缀。

    两者的参数要求相同,第一个为你要映射到的handler,第二个为要添加映射的路径(支持正则表达式),然后约束条件与HttpSimpleController的相同,这里就不多赘述了。

  • 除了路径,我们还可以对参数进行映射:

    • 首先是路径参数和问号后的请求参数,这两种参数的映射有四种方式:

      • {}:这种只有大括号的,会将对应参数映射到对应参数位置
      • {1}, {2} :大括号中带有数字的,会将对应参数映射到对应数字指定的参数位置
      • {name}:中间的字符串没有实际作用,但能提高程序的可读性
      • {1:name1}, {2:name2}:冒号后的字符串同样没有实际作用,但能提高程序的可读性

      例如:"/{1:username}/test?token={2:token}"

      需要注意的是,目标参数类型只能是基本类型,std:string类型或任何可以使用stringstream >>操作符赋值的类型

    • 另外,drogon还提供了从HttpRequestPtr对象到任意类型的参数映射机制,当handler要求的参数大于路径中的参数映射时,后面多余的参数会有HttpRequestPtr对象转换而来。用户可以定义任意类型的转换,定义这种转换的方式是特化drogon命名空间的fromRequest模板(定义于HttpRequest.h头文件)。比如我们定义一个user的结构体:

      User.h

      #pragma once
      #include <iostream>
      #include <drogon/HttpRequest.h>
      
      struct User {
          std::string userId;
          std::string account;
          std::string passwd;
      };
      
      namespace drogon {
          template <>
          inline User fromRequest(const HttpRequest& req) {
              auto json = req.getJsonObject();
              User user;
              if (json) {
                  user.userId = (*json)["userId"].asString();
                  user.account = (*json)["account"].asString();
                  user.passwd = (*json)["passwd"].asString();
              }
              return user;
          }
      }
      

然后下面是一个例子:

HttpTestController.h

#pragma once

#include <drogon/HttpController.h>
#include "User.h"

using namespace drogon;

class HttpTestController : public drogon::HttpController<HttpTestController>
{
  public:
      METHOD_LIST_BEGIN
          // 添加两个路径映射
          ADD_METHOD_TO(HttpTestController::setInfo, "/user/set", Post);
          ADD_METHOD_TO(HttpTestController::getInfo, "/{1:userId}/info?token={2}", Get);
      METHOD_LIST_END
      void setInfo(const HttpRequestPtr& req,
          std::function<void(const HttpResponsePtr&)>&& callback,
          User&& user);
      void getInfo(const HttpRequestPtr& req,
          std::function<void(const HttpResponsePtr&)>&& callback,
          std::string userId,
          const std::string& token) const;
};

HttpTestController.cc

#include "HttpTestController.h"

void HttpTestController::setInfo(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback, User&& user) {
	auto resp = HttpResponse::newHttpResponse();
	resp->setStatusCode(k200OK);
	resp->setContentTypeCode(ContentType::CT_TEXT_HTML);
	if (user.userId.empty() || user.account.empty() || user.passwd.empty()) {
		resp->setBody("Error!");
	}
	else {
		resp->setBody("User " + user.userId + " saved!");
	}
	callback(resp);
}

void HttpTestController::getInfo(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback, std::string userId, const std::string& token) const {
	auto resp = HttpResponse::newHttpResponse();
	resp->setStatusCode(k200OK);
	resp->setContentTypeCode(ContentType::CT_TEXT_HTML);
	resp->setBody("id: " + userId + "<br/>account: " + "123456" + "<br/>passwd: " + "******" + "<br/>token: " + token);
	callback(resp);
}

结果如下:

在这里插入图片描述

在这里插入图片描述

3、WebSocketController

熟悉计算机网络的读者应该对websocket连接不陌生,这是基于HTTP的一种长连接方案,只有在连接建立时进行一次HTTP格式的请求和应答交换,之后所有的消息传输都在websocket上进行。

同样的,WebSocketController可以通过drogon_ctl快速生成:

drogon_ctl create controller -w WebSocketTestController

生成的文件中可以看到drogon已经给我提供好的三个handler

  • handlerNewConnection是在websocket连接建立后被调用的,参数中的req是用户发来的建立请求,这时候框架已经帮我们返回了response,那么req就只能为我们提供一些额外信息了;wsConnPtr是这个websocket对象的一个智能指针,它有如下常用接口:

    // 发送websocket消息
    void send(const char *msg,uint64_t len);	// 消息内容和长度
    void send(const std::string &msg);	// 消息内容
    
    // 该websocket的本机和远端地址
    const trantor::InetAddress &localAddr() const;
    const trantor::InetAddress &peerAddr() const;
    
    // 该websocket的连接状态
    bool connected() const;
    bool disconnected() const;
    
    // 关闭该websocket
    void shutdown();
    void forceClose();
    
    // 设置和获取本websocket的上下文,由用户存入一些业务数据,
    // any类型意味着可以存取任意类型的对象。
    void setContext(const any &context);
    const any &getContext() const;
    any *getMutableContext();
    
  • handleNewMessage是在websocket接收到新消息后被调用的,message是消息接收到的消息(这个message是完整的消息净荷,框架已经做完了消息的解封包和解码等工作,用户直接处理消息本身即可),type顾名思义是消息的类型,通过查看WebSocketConnection.h的源码,我们可以发现它默认为0,对应text格式。

  • handleConnectionClosed是在websocket连接关闭后被调用的,我们可以在里面做一些收尾工作。

除了handler,生成的文件中还有添加路径映射的宏:

  • 我们可以通过WS_PATH_ADD宏把这个控制器注册到某个路径上,这个宏的用法与之前的类似,它的参数为要映射的路径和过滤器。

下面是一个例子:

WebSocketTestController.h

#pragma once

#include <drogon/WebSocketController.h>

using namespace drogon;

class WebSocketTestController : public drogon::WebSocketController<WebSocketTestController>
{
  public:
     void handleNewMessage(const WebSocketConnectionPtr&,
                                  std::string &&,
                                  const WebSocketMessageType &) override;
    void handleNewConnection(const HttpRequestPtr &,
                                     const WebSocketConnectionPtr&) override;
    void handleConnectionClosed(const WebSocketConnectionPtr&) override;
    WS_PATH_LIST_BEGIN
        WS_PATH_ADD("/chat");
    WS_PATH_LIST_END
};

WebSocketTestController.cc

#include "WebSocketTestController.h"
#include <time.h>

void WebSocketTestController::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr, std::string &&message, const WebSocketMessageType &type)
{
    // 获取当前时间
    time_t timep;
    tm* p;
    time(&timep);
    p = localtime(&timep);
    std::string strTime = std::format("[{}/{}/{} {}:{}] ", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday, p->tm_hour, p->tm_min);

    wsConnPtr->send(strTime + message);
}

void WebSocketTestController::handleNewConnection(const HttpRequestPtr &req, const WebSocketConnectionPtr& wsConnPtr)
{
    wsConnPtr->send("Welcome!");
}

void WebSocketTestController::handleConnectionClosed(const WebSocketConnectionPtr& wsConnPtr)
{
}

结果如下:

在这里插入图片描述

二、过滤器(filter)

过滤器(filter),顾名思义,能够为我们提供提供过滤用户请求的功能,它可以帮助我们提高编程效率,例如要实现多个业务功能,但这些功能都要用户登录了才能使用,我们就可以在每个功能的请求路径上加上一个用户登录的过滤器。

在drogon中,drogon框架做完URL路径匹配后,会先依次调用注册到该路径上的过滤器,只有当所有过滤器都允许"通过"时,对应的handler才会被调用。

1、内置过滤器

drogon框架本身就内置了几个过滤器,供我们直接使用,常用的内置过滤器有:

  • drogon::IntraneIpFilter: 只放行内网ip发来的http请求,否则返回404页面

  • drogon::LocalHostFilter: 只放行本机127.0.0.1发来的http请求,否则返回404页面

2、自定义过滤器

除了内置的过滤器,我们还可以自定义过滤器,只需要继承HttpFilter类模板。

自定义格式如下:

class LoginFilter:public drogon::HttpFilter<LoginFilter>
{
public:
    virtual void doFilter(const HttpRequestPtr &req,
                          FilterCallback &&fcb,
                          FilterChainCallback &&fccb) override ;
};

当然,我们也可以通过drogon_ctl快速创建:

drogon_ctl create filter LoginFilter

可以看到,创建的过滤器有一个虚函数,要实现过滤器的逻辑就要重载该函数,该函数一共有三个参数:

  • req: http请求
  • fcb: 过滤器回调函数,当请求被过滤器过滤掉时,通过这个回调来给用户返回特定响应
  • fccb: 过滤器链回调函数,当请求通过过滤器时,通过这个回调告诉drogon调用下一个过滤器或者最终的handler

创建了过滤器后,我们还需要将其配置到路径上,就和前面提到的一样,只需要在添加路径映射的宏的参数中加上过滤器就行,就像这样:

ADD_METHOD_TO(HttpTestController::getInfo, "/{1:userId}/info?token={2}", Get, "LoginFilter");

注意:如果过滤器在定义的命名空间里,这里还需要再加上命名空间

下面是一个具体的例子,创建一个过滤未登录用户的过滤器:

LoginFilter.h

#pragma once

#include <drogon/HttpFilter.h>
using namespace drogon;

class LoginFilter : public HttpFilter<LoginFilter> {
  public:
    LoginFilter() {}
    void doFilter(const HttpRequestPtr &req,
                  FilterCallback &&fcb,
                  FilterChainCallback &&fccb) override;
};

LoginFilter.cc

void LoginFilter::doFilter(const HttpRequestPtr &req,
                          FilterCallback &&fcb,
                          FilterChainCallback &&fccb) {
    // 检查用户是否已经登录
    std::string userId = req->getParameter("token");
    if (userId.empty()) {
    	// 用户未登录,返回提示信息
    	auto resp = HttpResponse::newHttpResponse();
    	resp->setStatusCode(k302Found);
    	resp->setBody("Not logged in, about to jump to the login page...");
    	fcb(resp);
	}
	else {
    	// 用户已登录,继续处理请求
    	fccb();
	}
}

然后将这个过滤器配置到路径上:

ADD_METHOD_TO(HttpTestController::getInfo, "/{1:userId}/info?token={2}", Get, "LoginFilter");

实现效果如下:

未登录

在这里插入图片描述

已登录

在这里插入图片描述

三、视图(view)

视图是一种常见的软件设计模式中的组件,它常被用于MVC(Model-View-Controller)架构中,它的主要职责是展示数据给用户,并接收用户的输入。虽然最近几年前后端分离的架构模式很流行,但是这并不意味着我们可以不再使用MVC了,因为前后端分离和MVC并不矛盾,我们可以根据项目需求合理搭配使用。

在drogon中,为了实现视图,drogon定义了一种csp(C++ Server Page)描述语言,它与jsp类似,都可以将代码嵌入到HTML页面中,不过csp嵌入的是c++而不是java。

1、CSP

drogon的csp方案很简单,我们用特殊的标记符号把C++代码嵌入到HTML页面里就可以了:

  • <%inc %>

    这个标签里的内容会被视为需要引用的头文件部分,这里只能写入#include语句,如<%inc#include "xx.h" %>,不过很多常见的头文件drogon都自动包含了,我们就不需要再添加了。

  • %<c++ %>

    这个标签里的内容会被视为C++的代码,如<c++ std:string name="drogon"; %>

    C++的代码一般都会原封不动的转移到目标源文件中,除了下面两种特殊标记:

    • @@:表示控制器传过来的data变量,类型是HttpViewData,可以从中获取需要的内容
    • $$:表示页面内容的流对象,可以把需要显示的内容通过<<操作符显示在页面上
  • [[ ]]

    这个标签里的内容会被视为变量名,view会以这个变量名为keyword从控制器传过来的数据里找到对应的变量,并把它输出到页面的对应位置,变量名字前后的空格会被省略;同时,出于性能考虑,只支持三种字符串数据类型const char *, std::stringconst std::string。另外,这对标签不要分行写。

  • {% %}

    这个标签里的内容会被直接视为C++程序里的变量名或表达式,而不是keyword,view会把该变量的内容或表达式的值输出到页面的对应位置。因此,{% val.xx %}等效于<%c++ $$<<val.xx; %>。另外,这对标签不要分行写。

  • <%view %>

    这个标签里的内容会被视为子视图的名字,drogon会找到相应的子视图并把它的内容填充到该标签所在位置;子视图和父视图共用控制器的数据, 可以多级嵌套但不要循环嵌套。另外,这对标签不要分行写。

  • <%layout %>

    这个标签里的内容会被视为布局的名字,,框架会找到相应的布局并把本视图的内容填充到该布局的某个位置。另外,这对标签不要分行写,可以多级嵌套但不要循环嵌套。

2、视图的使用

首先要将写好的CSP文件转换成C++源文件,只需使用drogon_ctl即可:

drogon_ctl create view xxx.csp

然后使用视图渲染响应,只需调用如下接口:

static HttpResponsePtr newHttpViewResponse(const std::string &viewName,
                                           const HttpViewData &data);

它有两个参数:

  • viewName:视图的名字,扩展名.csp可省略
  • data:控制器的handler传给视图的数据,类型是HttpViewData,这是个特殊的map,可以存入和取出任意类型的对象

可以看到,控制器不需要引用视图的头文件,控制器和视图实现了很好的解耦;他们唯一的联系是data变量,对data的内容,控制器和视图要有一致的约定。

我们以之前注册的/{userId}/info路径为例,为其创建一个视图:

UserInfo.csp

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>[[ title ]]</title>
</head>
<body>
    <H1>UserInfo</H1>
    <table>
        <tr>
            <th>userId</th>
            <td>[[ userId ]]</td>
        </tr>
        <tr>
            <th>account</th>
            <td>[[ account ]]</td>
        </tr>
        <tr>
            <th>passwd</th>
            <td>[[ passwd ]]</td>
        </tr>
        <tr>
            <th>token</th>
            <td>[[ token ]]</td>
        </tr>
    </table>
</body>
</html>

HttpTestController::getInfo

void HttpTestController::getInfo(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback, const std::string& userId, const std::string& token) const {
	HttpViewData data;
	data.insert("title", "UserInfo");
	data.insert("account", "admin");
	data.insert("passwd", "admin");
	data.insert("userId", userId);
	data.insert("token", token);
	auto resp = HttpResponse::newHttpViewResponse("UserInfo.csp", data);
	callback(resp);
}

然后使用drogon_ctl将csp文件转换成c++源文件:

drogon_ctl create view UserInfo.csp

cmake构建一下,运行,结果如下:

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于C ++ 14/17的HTTP应用程序框架drogonDrogon可用于使用C ++轻松构建各种类型的Web应用程序服务器程序。 Drogon是一个跨平台框架,它支持Linux,macOS,FreeBSD和Windows。其主要特点如下: *使用基于epoll的非阻塞I / O网络库(macOS / FreeBSD下的kqueue)提供高并发,高性能的网络IO,请访问[TFB测试结果](https://www.techempower。 com / benchmarks /#section = data-r19&hw = ph&test = composite)以获取更多详细信息; *提供完全异步的编程模式; *支持Http1.0 / 1.1(服务器端和客户端); *基于模板,实现了一种简单的反射机制,以完全解耦主程序框架控制器视图。 *支持cookie和内置会话; *支持后端渲染,控制器将数据生成到视图以生成Html页面。视图由CSP模板文件描述,C ++代码通过CSP标记嵌入到HTML页面中。 drogon命令行工具会自动生成C ++代码文件进行编译; *支持视图页面动态加载(运行时动态编译和加载); *提供从路径到控制器处理程序的便捷灵活的路由解决方案; *支持过滤器链,以方便在处理HTTP请求之前执行统一的逻辑(例如登录验证,Http方法约束验证等); *支持https(基于OpenSSL); *支持WebSocket(服务器端和客户端); *支持JSON格式的请求和响应,对Restful API应用程序开发非常友好; *支持文件下载和上传; *支持gzip,brotli压缩传输; *支持流水线; *提供轻量级的命令行工具drogon_ctl,以简化Drogon中各种类的创建以及视图代码的生成; *支持基于非阻塞I / O的异步读写数据库(PostgreSQL和MySQL(MariaDB)数据库); *支持基于线程池的异步读写sqlite3数据库; *支持ARM体系结构; *提供方便的轻量级ORM实现,支持常规的对象到数据库双向映射; *支持可在加载时由配置文件安装的插件; *通过内置连接点支持AOP。
Drogon是一个基于C++14/17的Http应用框架,使用Drogon可以方便的使用C++构建各种类型的Web应用服务端程序。 本版本库是github上Drogon工程的镜像库。Drogon是作者非常喜欢的美剧《权力的游戏》中的一条龙的名字(汉译作卓耿),和龙有关但并不是dragon的误写,为了不至于引起不必要的误会这里说明一下。 Drogon是一个跨平台框架,它支持Linux,也支持macOS、FreeBSD,和Windows。它的主要特点如下: 1、网络层使用基于epoll(macOS/FreeBSD下是kqueue)的非阻塞IO框架,提供高并发、高性能的网络IO。详细请见TFB Tests Results; 2、全异步编程模式; 3、支持Http1.0/1.1(server端和client端); 4、基于template实现了简单的反射机制,使主程序框架控制器(controller)和视图(view)完全解耦; 5、支持cookies和内建的session; 6、支持后端渲染,把控制器生成的数据交给视图生成Html页面,视图由CSP模板文件描述,通过CSP标签把C++代码嵌入到Html页面,由drogon的命令行工具在编译阶段自动生成C++代码并编译; 7、支持运行期的视图页面动态加载(动态编译和加载so文件); 8、非常方便灵活的路径(path)到控制器处理函数(handler)的映射方案; 9、支持过滤器(filter)链,方便在控制器之前执行统一的逻辑(如登录验证、Http Method约束验证等); 10、支持https(基于OpenSSL实现); 11、支持websocket(server端和client端); 12、支持Json格式请求和应答, 对Restful API应用开发非常友好; 13、支持文件下载和上传,支持sendfile系统调用; 14、支持gzip/brotli压缩传输; 15、支持pipelining; 16、提供一个轻量的命令行工具drogon_ctl,帮助简化各种类的创建和视图代码的生成过程; 17、基于非阻塞IO实现的异步数据库读写,目前支持PostgreSQL和MySQL(MariaDB)数据库; 18、基于线程池实现sqlite3数据库的异步读写,提供与上文数据库相同的接口; 19、支持ARM架构; 20、方便的轻量级ORM实现,支持常规的对象到数据库的双向映射操作; 21、支持插件,可通过配置文件在加载期动态拆装; 22、支持内建插入点的AOP

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值