catalinaの備忘録

ソフトウェアやハードウェアの備忘録。後で逆引きできるように。

C++のHTTPサーバ(boost::beast)を試す

毎年恒例の夏バテの時期がやってきました。かたりぃなです。 今回はC++でhttpやってみたいと思います。

C++でhttpサーバ

今はC++単体でラクにHTTPサーバが実装できるようになりました。

使うライブラリはboost::beastです。

https://www.boost.org/doc/libs/1_67_0/libs/beast/doc/html/index.html

boost1.66で入ったので、結構最近です。

exampleをビルドして動かす

とりあえず適当な仮想マシン上でサンプルコードをビルドして、実行してみます。

example/http/server/sync/http_server_sync.cpp あたりが単純で分かりやすいです。

これを実行したディレクトリのファイルをHTTP-GETできます。

まるでPythonのSimpleHTTPServerみたいですね。

足りない

私がやりたいことに少々足りません。

Hololensから画像を投げて、それを受け取って処理するWebサーバが欲しいのです。そう、PUTとPOSTの実装がサンプルコードにはないのです。

ないなら作りましょう。

サンプルコードの改造でサクッと。

POSTを受け付ける

サンプルコードではhandle_requestという関数があります。 第二引数がまさにHTTPリクエストで、一通りの情報が入ってます。

http::request<Body, http::basic_fields<Allocator>>&& req

サンプルコードではreq.method() == http::verb::getとかやってるので真似しましょう。

    if( req.method() == http::verb::post){
        std::cerr << "POST request" << std::endl;
    }

実際のHTTPメソッドの定義はこうなってます。 https://www.boost.org/doc/libs/develop/boost/beast/http/verb.hpp

データを投げるテストをするならこんな感じで。

curl -X POST localhost:port --data-binary @/home/catalina/imgfile.jpg --verbose

これでサーバを起動したマシン側でPOST requestという文字列が出力されます。

POSTのbodyでバイナリを受け付ける用意をする

サンプルコードでは、HTTP-Requestには文字列が入ってくる前提になっています。画像を受けたいのでここの型を変更します。

まだこのライブラリ使い慣れてないので、bodyにpngファイルがバイナリで1つだけ入っている という前提でいきます。

実現方法としては型を変更してあげればよいです。 大本の呼び出し元の型を変えてあげれば、関連するテンプレート関数が一通り置き換わっていくことになります。

いいですね。テンプレート。(コンパイルエラーになるとメッセージが長すぎて怒られてる気がしてくるけど。)

sessionクラス内で定義されているreq_の型を変更します。

    http::request<http::vector_body<unsigned char> > req_;

これでHTTP-bodyはstd::vectorとしてアクセスできます。

HTTP-Bodyを取り出す

さきほど作ったPOSTの条件分岐のところでBodyを取り出します。

こんな感じで

    // デバッグ用。サイズが一致しているか目視確認したい
    auto size = req.body().size();
    std::cerr << "bodysize = " << size << std::endl;

    // 受信した画像ファイルをOpenCVのmatにする
    cv::Mat rawdata(req.body() );
    auto img = cv::imdecode(rawdata, 1);
    cv::imwrite("received_image.png", img);

今回は直接cv::Matを作りましたが、当然std::vectorで取り出すこともできます。

できた?

思ったより簡単にできてしまいました。

せっかくなので、レスポンスも作っちゃいましょう。

サンプルコードから適当なところをコピって貼れば動く気がします。雑に見えるのは飽きてきたからです。

適当なJSONレスポンスを返す

サーバで画像を受け取りたい理由は、計算資源が潤沢なサーバで画像を解析して、結果をHololensに返したいからでした。

というわけで、「返すべきデータを何か準備できる」という想定のもとJSONを返してみます。

        // レスポンスを返す
        http::string_body::value_type   body;
        body = "{'result':'ok'}";               // TODO : ここでbodyを設定する
        http::response<http::string_body> res{
            std::piecewise_construct,
            std::make_tuple(std::move(body)),
            std::make_tuple(http::status::ok, req.version())};
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, "application/json");
        res.prepare_payload();                  // これ呼ばないとBodyがカラになる
        res.keep_alive(req.keep_alive());
        return send(std::move(res));

なんかあっさりいけました。

感想と今後の展望

最近ご無沙汰していたBoostですが、やっぱり楽しいですね。

エラーメッセージが長すぎて泣きそうになりますが、追っていくとメタな実装になっていて「そのテがあったか」と思わされることが多々あります。

またコンパイル時に型チェックが入るので、仕様から実装した段階で抜けがあると気づけるのもいいところだと思っています。

(たとえばrequestのbodyをfile_body型にすると、std::vectorみたいな使い方はできなくなるので、実装時点でマズイということに気づくきっかけになったりとか。)

今後の展望としては、もはやC++だけでなんでもできるので、実験がはかどります。

クライアント側もサーバ側も同じ言語(C++)で作れるので、性能面はプロトタイプ実装が終わってからでも大丈夫だろうと思っています。モジュールの配置場所を変えるだけなので。 (ただし、モジュール間の結合が疎であるという大前提が必要)

Hololensもバージョンアップきてましたし、いろいろ試してみたいですね。それでは今回はこれくらいで。