c++web服务器框架Oat++的API应用

引言

这是一个轻量且高效的c++web服务框架,本篇文章会讲述如何使用其API,并且是一部分的原理和其中的架构。

代码讲解

首先我们先构建一个简单的架构,这个简单的架构由四个部分组成

router:路由器,这个负责接受我们的路径然后进行对应的匹配,匹配成功之后交给业务处理函数。

connectionHandler:这个是负责解析客户的请求的,然后才交给router来处理,里面的工作一个是要解析请求数据,然后调用对应的router。

connectionProvider:这个是负责监听客户的连接的,通过socket的accept(),当有对象来的时候就把对象封装成oatpp::network::Connection,然后交给connectionHandler

cpp 复制代码
#include "oatpp/web/server/HttpConnectionHandler.hpp"

#include "oatpp/network/Server.hpp"
#include "oatpp/network/tcp/server/ConnectionProvider.hpp"

void run() {

  /* Create Router for HTTP requests routing */
  auto router = oatpp::web::server::HttpRouter::createShared();

  /* Create HTTP connection handler with router */
  auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

  /* Create TCP connection provider */
  auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({"localhost", 8000, oatpp::network::Address::IP_4});

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Print info about server port */
  OATPP_LOGI("MyApp", "Server running on port %s", connectionProvider->getProperty("port").getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}

router不是要匹配对应的请求吗,我们这里假设请求方式就是GET,路径是/hello,然后传入处理的函数的指针,这个处理函数里面,我们返回一个状态码和我们要输出的东西

cpp 复制代码
class Handler : public oatpp::web::server::HttpRequestHandler {
public:

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr<OutgoingResponse> handle(const std::shared_ptr<IncomingRequest>& request) override {
    return ResponseFactory::createResponse(Status::CODE_200, "Hello World!");
  }

};

  auto router = oatpp::web::server::HttpRouter::createShared();
  
  /* Route GET - "/hello" requests to Handler */
  router->route("GET", "/hello", std::make_shared<Handler>());

然后这一个部分是序列化和反序列化的一个过程,我们这里涉及到了一个预编译的操作,就是在这个领域内,我们会提前采取预编译的操作。

然后这里我们使用DTO的形式传递数据,我们上面打印的hello world是纯文本的形式,那如果我们需要传输结构化的数据(例如JSON),我们就需要DTO

cpp 复制代码
#include OATPP_CODEGEN_BEGIN(DTO)

/**
 * Message Data-Transfer-Object
 */
class MessageDto : public oatpp::DTO {

  DTO_INIT(MessageDto, DTO /* Extends */)

  DTO_FIELD(Int32, statusCode);   // Status code field
  DTO_FIELD(String, message);     // Message field

};

/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

如何使用,如下:

cpp 复制代码
ENDPOINT("GET", "/hello", getHello) {
    // 创建 DTO 对象
    auto dto = MessageDto::createShared();
    dto->statusCode = 200;
    dto->message = "Hello World!";
    
    // 用 createDtoResponse 自动序列化成 JSON
    return createDtoResponse(Status::CODE_200, dto);
}

不过在官方给的文档里面,我们有一些改进的地方:

首先就是加入了objecterMapper,这个其实就是一个帮忙处理序列化和反序列化的东西。

cpp 复制代码
class Handler : public oatpp::web::server::HttpRequestHandler {
private:
  std::shared_ptr<oatpp::data::mapping::ObjectMapper> m_objectMapper;
public:

  /**
   * Constructor with object mapper.
   * @param objectMapper - object mapper used to serialize objects.
   */
  Handler(const std::shared_ptr<oatpp::data::mapping::ObjectMapper>& objectMapper)
    : m_objectMapper(objectMapper)
  {}

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr<OutgoingResponse> handle(const std::shared_ptr<IncomingRequest>& request) override {
    auto message = MessageDto::createShared();
    message->statusCode = 1024;
    message->message = "Hello DTO!";
    return ResponseFactory::createResponse(Status::CODE_200, message, m_objectMapper);
  }

};
auto objectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared();
 router->route("GET", "/hello", std::make_shared<Handler>(objectMapper /* json object mapper */ ));

以上就是我们的一个基本的架构,接下来就是一些具体的实现了

OATPP_CREATE_COMPONENT是用来初始化环境的,并在需要的地方注入OATPP_COMPONENT。

前者是创建一个实体组件放到全局容器里面,后者是从全局容器里面取出对应的组件

大家可以看到第一个部分传递的参数有主机的名称,有端口号,有通信协议,说明这个就是与客户端监听的地方。然后所有的部分里面只有ConnectionHandler不仅仅返回了实体,还创建了router,这个是因为每个ConnectionHandler都会对应一个router,解析完数据之后就会立刻调用router,根据路径匹配对应的业务函数,所以这里我们创建的时候要传递router,这样子每个ConnectionHandler都会有对应路由器的指针

cpp 复制代码
class AppComponent {
public:

  /**
   *  Create ConnectionProvider component which listens on the port
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([] {
    return oatpp::network::tcp::server::ConnectionProvider::createShared({"localhost", 8000, oatpp::network::Address::IP_4});
  }());

  /**
   *  Create Router component
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, httpRouter)([] {
    return oatpp::web::server::HttpRouter::createShared();
  }());

  /**
   *  Create ConnectionHandler component which uses Router component to route requests
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, serverConnectionHandler)([] {
    OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router); // get Router component
    return oatpp::web::server::HttpConnectionHandler::createShared(router);
  }());

  /**
   *  Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, apiObjectMapper)([] {
    return oatpp::parser::json::mapping::ObjectMapper::createShared();
  }());

};
cpp 复制代码
  /* Register Components in scope of run() method */
  AppComponent components;

  /* Get router component */
  OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);

  /* Route GET - "/hello" requests to Handler */
  router->route("GET", "/hello", std::make_shared<Handler>());

  /* Get connection handler component */
  OATPP_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, connectionHandler);

  /* Get connection provider component */
  OATPP_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, connectionProvider);

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

接下来我们就要用API创建一个请求处理层(controller),这里我们会直接调用接口来实现:

首先这个是一个基本的框架,我们的所有实现都是在这个上面。首先是一个构造函数,这个构造函数里面初始化了一个 objectMapper,这个的目的就是后面的createresponse里面反序列化和序列化的操作

cpp 复制代码
#include "oatpp/web/server/api/ApiController.hpp"
#include "oatpp/core/macro/codegen.hpp"

#include OATPP_CODEGEN_BEGIN(ApiController) ///< Begin ApiController codegen section

class MyController : public oatpp::web::server::api::ApiController {
public:

  MyController(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper) /* Inject object mapper */)
    : oatpp::web::server::api::ApiController(objectMapper) 
  {}

  ENDPOINT("GET", "/", root) {
    return createResponse(Status::CODE_200, "Hello World!");
  }
  
  // TODO - more endpoints here

};

#include OATPP_CODEGEN_END(ApiController) ///< End ApiController codegen section

我们这里写一个认证的接口,就是判断用户是否符注册需求,我们这里的端点结构选择这个:

需要额外说的就是最后一个参数,就是我们的业务函数QUERIES(QueryParams, queryParams)第一个参数是传递的参数类型,第二个就是参数本身。

cpp 复制代码
ENDPOINT("GET", "/users", getUsers,
         QUERIES(QueryParams, queryParams)) 
{
  for(auto& param : queryParams.getAll()) {
    OATPP_LOGD("param", "%s=%s", param.first.getData(), param.second.getData());
  }
  return createResponse(Status::CODE_200, "OK");
}

当然还有另外一种表达方式就是

cpp 复制代码
ENDPOINT("GET", "/users", getUsers,
         QUERY(Int32, age, "user-age")) 
{
  OATPP_LOGD("Test", "age=%d", age->getValue());
  return createResponse(Status::CODE_200, "OK");
}

所以我们的大致模板就是:

cpp 复制代码
class MyController : public oatpp::web::server::api::ApiController {
public:

  MyController(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper) /* Inject object mapper */)
    : oatpp::web::server::api::ApiController(objectMapper) 
  {
    setDefaultAuthorizationHandler(std::make_shared<MyBasicAuthorizationHandler>());
  }

  ENDPOINT("GET", "/my/secret/resource", getResource,
           AUTHORIZATION(std::shared_ptr<MyAuthorizationObject>, authObject)) 
  {
    OATPP_ASSERT_HTTP(authObject->userId == "uid-admin", Status::CODE_401, "Unauthorized");
    return createResponse(Status::CODE_200, "OK");
  }

};

然后我们需要生成API文档页面,所以需要调用

cpp 复制代码
ENDPOINT_INFO(createUser) {
  info->summary = "Create new User";
  info->addConsumes<Object<UserDto>>("application/json");
  info->addResponse<Object<UserDto>>(Status::CODE_200, "application/json");
}
ENDPOINT("POST", "demo/api/users", createUser,
         BODY_DTO(Object<UserDto>, userDto)) {
  return createDtoResponse(Status::CODE_200, m_database->createUser(userDto));
}

最后的代码差不多就是这个样子:

cpp 复制代码
#include OATPP_CODEGEN_BEGIN(ApiController) ///< Begin ApiController codegen section

class controller : public oatpp::web::server::api::ApiController {
public:
    // 定义访问的入口
    controller(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper) /* Inject object mapper */)
        : oatpp::web::server::api::ApiController(objectMapper)
    {
        setDefaultAuthorizationHandler(std::make_shared<BasicAuthorizationHandler>("my-realm"));
    }

    static std::shared_ptr<controller> createShared(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper)) {
         return std::make_shared<controller>(objectMapper);
    }

    ENDPOINT_INFO(putResource) {
        info->summary = "Put Resource";
        info->addConsumes<ObjectMapper::Info>("application/json");
        info->addResponse<String>(Status::CODE_200, "text/plain");
    }

	// 定义查询接口描述
    ENDPOINT("PUT", "/test", putResource, AUTHORIZATION(std::shared_ptr<DefaultBasicAuthorizationObject>, authObject)) {
        OATPP_ASSERT_HTTP(authObject->userId == "uid-admin", Status::CODE_401, "Unauthorized");
        return createResponse(Status::CODE_200, "OK");
    }
};

#include OATPP_CODEGEN_END(ApiController) ///< End ApiController codegen section
相关推荐
m0_738120721 小时前
AI安全实战系列(四):Lab04 Multi-Agent——多智能体攻击分析
服务器·开发语言·人工智能·安全·网络安全·语言模型
泰和英杰1 小时前
2026 华为数字能源实操考点迭代:数据中心与光伏储能标准化故障排查方法论
运维·服务器·网络
心中有国也有家1 小时前
AtomGit Flutter 鸿蒙客户端:MoodStorage 的 CRUD 集成验证
android·服务器·flutter·华为·harmonyos
Irissgwe1 小时前
算法之二分查找
数据结构·c++·算法·二分查找
zh_xuan2 小时前
c++ span的用法
开发语言·c++·span
进击的荆棘2 小时前
Linux——基础指令(中)
linux·服务器·linux基础指令
b130538100492 小时前
鸿蒙应用开发实战【41】— 登录页面LoginPage完整开发
服务器·华为·harmonyos·鸿蒙系统
t-think2 小时前
一起think C++:C++基础篇(一)—— namespace、IO、缺省参数与函数重载
c++
zt1985q2 小时前
本地部署开源轻量级监控平台 Beszel 并实现外部访问
运维·服务器·网络协议·开源