C++のWeb Framework oat++ で Hello World
大きめのデータ(といっても100万点のオーダだけど)を扱うWebアプリを作りたいのだが Pythonだと遅いので、C++で書きたい。 ということで、oat++というC++で書かれたフレームワークがあったので、スケルトンコードを作成してみた。 ドキュメントもあまり詳しくなく、ちょっと苦労したので忘れないようにblogで公開しておく。
前述の通りoat++は、C++で書かれたWebフレームワークでパフォーマンスがべらぼうに良いらしい。 まぁネイティブコードなのでそりゃ速いよねー もちろんオープンソース。 さらに他のライブラリに依存しないのが使いやすそう。
oat++のWebページは、https://oatpp.io GitHubのリポジトリは、 https://github.com/oatpp/oatpp である。 今回は、Debian9 stretchでコンパイル、実行を行った。
インストール方法
sudo apt-get -y install build-essential cmake git
git clone https://github.com/oatpp/oatpp && mkdir oatpp/build
cd oatpp/build && cmake .. && make && sudo make install
必要なパッケージは、build-essentialとcmakeがあれば特にいらない。gitでcloneしてbuildするだけ。 かんたんかんたん。
Hello World
さて、ここからが本題。 今回作成したコードは、以下の3つのファイルである。 https://github.com/oatpp/example-crud を参考にした。
- app.cc
- app_component.hh
- controller.hh
app.cc
//app.cc
#include "oatpp/network/server/Server.hpp"
#include <iostream>
#include <string>
#include "./app_component.hh"
#include "./controller.hh"
void run() {
AppComponent components;
auto router = components.httpRouter.getObject();
auto controller = Controller::createShared();
controller->addEndpointsToRouter(router);
oatpp::network::server::Server server(components.serverConnectionProvider.getObject(), components.serverConnectionHandler.getObject());
server.run();
}
int main(int argc, const char * argv[]) {
oatpp::base::Environment::init();
run();
oatpp::base::Environment::destroy();
return 0;
まずは、main関数が含まれるapp.cc。サーバの初期化と起動を行っている。
app_component.hh
次に、app_component.hhは、app.ccのrun関数で、serverを初期化に必要なオブジェクトインスタンスを作成するクラスAppComponentを 定義している。
// app_conponent.hh
#pragma onece
#include <string>
#include "oatpp/web/server/HttpConnectionHandler.hpp"
#include "oatpp/web/server/HttpRouter.hpp"
#include "oatpp/network/server/SimpleTCPConnectionProvider.hpp"
#include "oatpp/core/macro/component.hpp"
/**
* Class which creates and holds Application components and registers components in oatpp::base::Environment
* Order of components initialization is from top to bottom
*/
class AppComponent {
private:
static const int PORT = 8000;
public:
/**
* Create ConnectionProvider component which listens on the port
*/
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([] {
return oatpp::network::server::SimpleTCPConnectionProvider::createShared(PORT);
}());
/**
* 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::server::HttpRouter>, serverConnectionHandler)([] {
OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router); // get Router component
return oatpp::web::server::HttpConnectionHandler::createShared(router);
}());
};
OATPP_CREATE_COMPONENT マクロは、oatpp/core/macro/component.hpp で以下のように定義されている。
#define OATPP_CREATE_COMPONENT(TYPE, NAME) \
oatpp::base::Environment::Component<TYPE> NAME = oatpp::base::Environment::Component<TYPE>
変数を宣言しているだけでした。
このクラスでは、ServerConnectionProvider、HttpRouter、HttpConnectionHandlerを作成している。
controller.hh
そして、controller.hhでは、URLマッピングと呼ばれたときの処理を記述する、Controllerクラスを定義している。
//controller.hh
#pragma onece
#include "oatpp/web/server/api/ApiController.hpp"
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
#include "oatpp/core/macro/codegen.hpp"
#include "oatpp/core/macro/component.hpp"
#include <sstream>
class Controller : public oatpp::web::server::api::ApiController {
public:
Controller(const std::shared_ptr<ObjectMapper>& objectMapper)
: oatpp::web::server::api::ApiController(objectMapper)
{}
static std::shared_ptr<Controller> createShared(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>,objectMapper))
{
return std::make_shared<Controller>(objectMapper);
}
#include OATPP_CODEGEN_BEGIN(ApiController)
ENDPOINT("GET", "/", root) {
const char* html =
"<html lang='en'>"
"<head>"
"<meta charset=utf-8/>"
"</head>"
"<body>"
"<p>Hello World</p>"
"</body>"
"</html>";
auto response = createResponse(Status::CODE_200, html);
response->putHeader(Header::CONTENT_TYPE, "text/html");
return response;
}
ENDPOINT("GET", "/get", get_test, REQUEST(std::shared_ptr<IncomingRequest>, request)
)
{
const char* resp = (request->getQueryParameter("name", ""))->c_str();
auto response = createResponse(Status::CODE_200, resp);
response->putHeader(Header::CONTENT_TYPE, "text/plain");
return response;
}
ENDPOINT("POST", "/post", post_test, BODY_STRING(String, request)
)
{
const char* resp = request->c_str();
auto response = createResponse(Status::CODE_200, resp);
response->putHeader(Header::CONTENT_TYPE, "text/plain");
return response;
}
ENDPOINT("GET", "/query-test", query_test,
QUERY(String, p, "lon"), QUERY(Float64, q, "lat")
)
{
std::stringstream resp;
resp<< "p="<<p->c_str()<< std::endl
<< "q="<<q<< std::endl;
auto response = createResponse(Status::CODE_200, resp.str().c_str());
response->putHeader(Header::CONTENT_TYPE, "text/plain");
return response;
}
ENDPOINT("GET", "/tile/${z}/${x}/${y}", path_test,
PATH(Int32, z), PATH(Int32, x), PATH(Int32, y), HEADER(Int32, param))
{
std::stringstream resp;
resp<< "zoomlevel = "<< z<< std::endl
<< "x = "<< x<< std::endl
<< "y = "<< y<< std::endl
<< "param = "<< param<< std::endl;
auto response = createResponse(Status::CODE_200, resp.str().c_str());
response->putHeader(Header::CONTENT_TYPE, "text/plain");
return response;
}
#include OATPP_CODEGEN_END(ApiController)
};
各、URLマッピングは、ENDPOINTマクロを使用する。
ENDPOINTマクロは、OATPP_CODEGEN_BEGIN()と、OATPP_CODEGEN_END()で囲む。
#include OATPP_CODEGEN_BEGIN(ApiController)
は、#include oatpp/codegen/codegen_define_ApiController_.hpp
に展開される。
oatpp/codegen_define_ApiController_.hppでは、ENDPOINTマクロで使用するマクロ類が宣言されている。
ENDPOINTマクロは、見てわかるように、#define ENDPOINT(METHOD, PATH, NAME, ...)
と宣言されており、
oatpp::web::protocol::http::outgoing::Response を返す関数に展開される。
4番目以降のパラメータは、パラメータマッピングで、関数での処理に必要なリクエストパラメータを指定する。
Parameter Mapping
oat++には、以下のパラメータマッピングがある
- ヘッダ
HEADER(<data-type>, <param-name>, "<optional header-name>")
- パス
PATH(<data-type>, <param-name>, "<optional path-variable-name>")
- クエリ
QUERY(<data-type>, <param-name>, "<optional path-variable-name>")
- Body
BODY_STRING(String, <param-name>)
- リクエスト
REQUEST(std::shared_ptr<IncomingRequest>, request)
- DTO
BODY_DTO(<DTO-class>::ObjectWrapper, <param-name>)
パラメータマッピングは、https://oatpp.io/docs/components/api-controller/ にドキュメントがあるが、
正直、どう書くのかほとんどわからなかった。
前掲のcontroller.hh
がサンプルになっていると思う。
data-typeは、以下の型が使えることを確認した。Booleanも使えるかも。
- String
- Int32
- Int64
- Float32
- Float64
Makefile
一応、Makefileも。
CXX=g++
CXXFLAGS=-I /usr/local/include/oatpp-0.19.4/oatpp --std=c++11 -w -Wall
LDFLAGS=-L /usr/local/lib/oatpp-0.19.4/ -loatpp -loatpp-test -lpthread
all: app
.o:.cc
$(CXX) -c $(CXXFLAGS) $<
app: app.o
$(CXX) -o $@ $< $(LDFLAGS)
app.o: app.cc app_component.hh
clean:
rm -f app *.o
Have Fan!