引言
这是一个轻量且高效的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