🦉

[C++] CrowとVSCodeでWebAPIを作る

2022/12/24に公開

https://crowcpp.org/master/
https://github.com/CrowCpp/Crow
Crowは、C++でWebAPI等を作れる、サーバサイドWebフレームワークです。

今回はごくシンプルなWebAPIを作るまでをゴールとします。本記事は公式の情報にほぼ沿っているのみで、VSCodeの手引きを添えているのが追加の情報です。

以下ハローワールドの例で、軽量言語によくあるフレームワークの心地です。

#include "crow.h"

int main()
{
    crow::SimpleApp app;

    CROW_ROUTE(app, "/")([](){
        return "Hello world";
    });

    app.port(18080).run();
}

サンプルコード

https://github.com/shimat/first_crow/tree/2022.12.24

筆者の環境

devcontainerなので、Dockerが動けば環境は問いません。

ホスト

  • Windows 11 + WSL2
  • Docker for Windows 4.15.0
  • Visual Studio Code 1.74.2

devcontainer内

VSCodeの拡張ではDev Containersをインストールします。
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers

手順

VSCodeのdevcontainerを作り、その中で動かすことにします。

Crow環境構築手順をDockerfileで定義

適当な空のディレクトリにて、Dockerfileを書きます。公式の手引きに沿って環境構築しています。Crowはソースコードからビルドしました。

Dockerfile
FROM ubuntu:22.04

ENV TZ=Asia/Tokyo
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt install -y --no-install-recommends \
  git \
  build-essential \
  cmake \
  libasio-dev \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

RUN git clone https://github.com/CrowCpp/Crow.git \
  && mkdir Crow/build \
  && cd Crow/build \
  && cmake .. -DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF \
  && make -j$(nproc) \
  && make install \
  && make clean \
  && cd && rm -rf Crow

devcontainerの構築

続いてVSCodeをそのディレクトリで開いて、コマンドパレットから Add Dev Container Configuration Files… を選択します。

From Dockerfileを選択します。次のダイアログは選択せずOKでよいです。

すると .devcontainer/devcontainer.json を作成してくれました。

このまま Reopen in container を押すと、docker buildでしばらく待たされたのち、devcontainerが立ち上がって準備完了です。

アプリケーションのコード

C++ソースコードを追加します。JSONのリクエストを受けて、足し算をして返すエンドポイントにしました(参考)。

main.cpp
#include <crow.h>

int main()
{
    crow::SimpleApp app;

    CROW_ROUTE(app, "/add_json")
      .methods(crow::HTTPMethod::Post)([](const crow::request& req) {
          auto x = crow::json::load(req.body);
          if (!x)
              return crow::response(400);
          auto sum = x["a"].i() + x["b"].i();
          std::ostringstream os;
          os << sum;
          return crow::response{os.str()};
      });

    app.port(80)
      .multithreaded()
      .run();
}

CMake

コンパイルは、コマンドを手打ちしてもよいですが、CMakeに頼ることにします。[1]

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(first_crow CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(${PROJECT_NAME} main.cpp)

find_package(Crow REQUIRED)

target_link_libraries(${PROJECT_NAME} PUBLIC Crow::Crow)

ビルド

$ mkdir build
$ cd build
$ cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /workspaces/first_crow/build
$ make
Consolidate compiler generated dependencies of target first_crow
[ 50%] Building CXX object CMakeFiles/first_crow.dir/main.cpp.o
<command-line>: warning: ISO C++11 requires whitespace after the macro name
[100%] Linking CXX executable first_crow
[100%] Built target first_crow

cmakeは、構成を大きく変えない限りは1回だけ実行すればよく、以後のリビルドはmakeのみでOKです。

実行

buildディレクトリの中に first_crow というバイナリファイルができているはずです。

$ ./first_crow
(2022-12-23 21:07:35) [INFO    ] Crow/master server is running at http://0.0.0.0:80 using 12 threads
(2022-12-23 21:07:35) [INFO    ] Call `app.loglevel(crow::LogLevel::Warning)` to hide Info level logs.
...

これでサーバーアプリケーションが起動しました。VSCodeのPORTSタブを見るとわかるはずです。

ホスト側で localhost:80 を叩けばこのWebAPIにリクエストできます。curlやPostmanなど何らかの方法で試してみてください。

$ curl -s -X POST http://localhost:80/add_json -d '{"a":1, "b":2}'
3

C++のサーバサイドWebフレームワークについて

最後にCrow以外も含めた状況を記しておきます。軽く調べたところ、以下3つが有力に思われました。
https://oatpp.io/
https://github.com/drogonframework/drogon
https://crowcpp.org/master/

上から順に"しっかりした"仕様と思います。Oat++はよく作りこまれていて、一般には一番おすすめかもしれません(そのうち記事にするかも)。DrogonはWebフレームワークのベンチマークにて最近最上位を占めることで知られます。CrowはFlask等に似てコントローラクラスもない簡単な書き味が特徴です。

以下私見ですが、C++によるサーバサイドの実装は流行っておらず、処理速度はよほどでなければRust等の他の選択肢があるはずで、動機というとC/C++の資産活用が一番に来るのではと思います。お世辞にもサーバアプリケーション開発体験は今主流の他言語に及ばないので、C++で作る範囲はマイクロサービス等の形で最小化し、主要部分は別の言語で書くのが無難と考えます。そうだとすれば、あまりがっちり作っても仕方ないからCrowにしよう、というのは選択肢になり得るのかもしれません。

備考: コード補完設定

以下を導入して設定することで、VSCodeでのC++開発を効率化します。
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools

.vscodeというディレクトリ(無ければ作成)の中に以下のような設定ファイルを置きました。

.vscode/settings.json
{
    "C_Cpp.clang_format_style": "file",
    "C_Cpp.default.cppStandard": "c++17",
    "editor.defaultFormatter": "ms-vscode.cpptools",
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true,
    "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools"
}
.vscode/c_cpp_properties.json
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "intelliSenseMode": "linux-gcc-x64",
            "configurationProvider": "ms-vscode.cmake-tools"
        }
    ],
    "version": 4
}

c_cpp_properties.jsonincludePath のところには、サードパーティライブラリ等で#includeの箇所がエラーになってしまう場合にそこにパスを追加すると良いです。

"C_Cpp.clang_format_style": "file" に対応する、フォーマット定義ファイルを置きます。なお始めの---は意味があり、書き間違いではありません。

.clang-format
---
BasedOnStyle: Microsoft    
IndentWidth: 4

改行やスペースの有無等、設定できる項目は多岐にわたります。以下を参照ください。
https://clang.llvm.org/docs/ClangFormatStyleOptions.html

脚注
  1. Zennのシンタックスハイライトでは、なぜかtarget_link_librariesのところで意図せぬ改行ができてしまいます。XX::YY のようなコロン2個を書くとこうなります。 ↩︎

Discussion