Drogonクライアント

DrogonでHTTPクライアントの使用方法を解説します。

DrogonでHTTPクライアントは次のようなコードでコルーチンを使用してコールバックの入れ子を避けて順序実行できます。

    #pragma once

    #include <drogon/drogon.h>
    #include <config/drogon_config.hpp>
    #include <utils/request.hpp>
    #include <list>
    #include <format/common/user.hpp>

    namespace spa::web::request::pplx {

      drogon::Task<std::list<spa::format::User>> get_users_request(drogon::HttpClientPtr client, drogon::HttpRequestPtr req)
      {
        req->setMethod(drogon::Get);
        req->setPath("/users/json");
        auto res = co_await client->sendRequestCoro(req);
        auto code = res->getStatusCode();
        if (code != drogon::HttpStatusCode::k200OK)
          throw code;
        spa::utils::request::session_cookies(req, res);
        std::list<spa::format::User> users;
        auto jsonPtr = res->getJsonObject();
        for (int i=0; i < jsonPtr->size(); i++)
          users.push_back(spa::format::User{(*jsonPtr)[i]});

        co_return users;
      }

    }


次のように処理フローを実行しています。

Getメソッド登録→リクエストパス設定→リクエスト送信→結果を返す。

続けて何度もリクエストを送信する場合でもco_awaitを繰り返してリクエストを送信できますね。

インターフェースは使いやすいです。

続いてつまづきポイントを解説します。

DrogonはノンブロッキングIOの非同期通信ライブラリです。

他の非同期ライブラリとしてAsioがありますが、Drogonの方は使う側は注意する点は一つだけで使用できます。

それがtrantor::EventLoopオブジェクトです。

EventLoopオブジェクトはapp().getLoop()で取得できます。

その後取得したtrantor::EventLoopオブジェクトにコールバック関数を登録します。

app().getLoop()でEventLoopオブジェクトを取得しqueueInLoopでタスクを登録します。

IO準備完了後ワーカースレッドで処理されます。

そして、EventLoopオブジェクトの領域はプログラムを通して一つだけ作成されるようにします。

getLoop()で取得するEventLoopオブジェクトのアドレスは静的記憶領域です。

クライアントの実装ではEventLoopを取得するソースを一つにまとめて実装しました。

次のコードはtrantorを使用するio_loop.hppとio_loop.cppです。

jsonリクエストを送信する場合、req->setBody(json.toStyledString())で後からBODYを設定しています。

    // io_loop.hpp
    #pragma once

    #include <thread>
    #include <drogon/drogon.h>
    #include <config/drogon_config.hpp>

    namespace spa::gui {

      class IOLoop
      {
      public:
        IOLoop();
        void queueInLoop(trantor::Func&&);
      public:
        drogon::HttpClientPtr client;
        drogon::HttpRequestPtr req;
      };

    }
  

    // io_loop.cpp
    #include <web/io_loop/io_loop.hpp>

    spa::gui::IOLoop::IOLoop()
    {
      std::thread([]{drogon::app().run();}).detach();
      client = drogon::HttpClient::newHttpClient(spa::config::web::URL);
      req = drogon::HttpRequest::newHttpJsonRequest(Json::Value{});
    }

    void spa::gui::IOLoop::queueInLoop(trantor::Func&& f)
    {
      drogon::app().getLoop()->queueInLoop(std::move(f));
    }


EventLoopオブジェクトの取得をIOLoopオブジェクトから必ず取得します。

理由は別々の共有オブジェクトから静的記憶領域にアクセスする場合、それぞれ別々の静的記憶領域になります。

非同期IOライブラリのtranterではこれを利用してEventLoopオブジェクトが生成済みかどうかのチェックを行いますので、別々の共有オブジェクトからEventLoopオブジェクトを取得しようとした場合に警告が出て処理が終了します。

そのため一つの共有オブジェクトないし、スタティックビルドのみで利用する必要があります。

たったこれを覚えておくだけでノンブロッキングIO通信が可能になる素晴らしいライブラリです。

10年前からノンブロッキングIOのフレームワークはnodejsしかないことを考えると今後このライブラリを使っていきたいですね。