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!