Pistache 是一個用 C++ 11 標準寫的 RESTful API library。除了 Server 以外,它也帶了不錯的 Client 端方法,不過今天主要會放在撰寫 Server 的部分。

為什麼要用 C++ 寫 API Server

最近在寫一個東西,原本只是要寫一個 Web 介面,然後讀檔寫檔而已。換句話說大概會有這樣的架構:

  • GET /:回傳 index.html
  • POST /save:把表單寫入到某個檔案。
  • GET /load:從某個檔案讀出 json 格式。

應該是個用 Koa 或 Flask 可以簡單寫完的東西。但是這次寫東西要在 Raspberry Pi Zero 上跑,而且它同時還有跑其他東西,CPU 都已經快吃完了,所以才會用 C++ 試著寫 RESTful Server。

環境安裝

首先你可能需要把 gcc、g++、make、cmake、git 安裝起來(以 Ubuntu 為例):

sudo apt-get install gcc g++ make cmake git

接著把 Pistache clone 下來並更新:

git clone https://github.com/oktal/pistache.git
cd pistache
git submodule update --init

接著安裝編譯並安裝 Pistache:

cd pistache
mkdir build
cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ..
make
sudo make install

這樣就能把 Pistache 安裝起來,稍後寫 C++ 的時候就可以直接 #include <pistache/xxx.h> 了。

Hello, world! 範例

這是官方 GitHub 給的 Hello, world! 範例,可以先試著打開一個檔案叫 server.cc,並貼上以下內容:

#include <pistache/endpoint.h>

using namespace Pistache;

struct HelloHandler : public Http::Handler {
  HTTP_PROTOTYPE(HelloHandler)
  void onRequest(const Http::Request&, Http::ResponseWriter writer) override{
    writer.send(Http::Code::Ok, "Hello, World!");
  }
};

int main() {
  Http::listenAndServe<HelloHandler>("*:9080");
}

接著使用 g++ 編譯:

g++ --std=c++11 server.cc -lpistache -o server

編譯好之後應該會看到一個 server 執行檔,直接執行 ./server 就可以在瀏覽器打開 https://localhost:9080 ,應該會看到一個 Hello, World! 字樣。

RESTful API 範例

官方的 GitHub 上也給了一個不錯好上手的 RESTful API 範例,程式碼有點長,這邊先貼一份連結 rest_server.cc

範例中在 setupRoutes 方法就定義好 router 的路徑了:

Routes::Post(router, "/record/:name/:value?", Routes::bind(&StatsEndpoint::doRecordMetric, this));
Routes::Get(router, "/value/:name", Routes::bind(&StatsEndpoint::doGetMetric, this));
Routes::Get(router, "/ready", Routes::bind(&Generic::handleReady));
Routes::Get(router, "/auth", Routes::bind(&StatsEndpoint::doAuth, this));

這邊看起來是 POST /record/:name/:value 時要執行 doRecordMetric 方法,看起來跟 Koa 的用法差不多。可以參考範例檔案裡面的設定來新增方法,例如:

class StatsEndpoint {
public:
    /* ... */
private:
    void setupRoutes () {
        Routes::Get(router, "/index.html", Routes::bind(&StatsEndpoint::doServe, this);
    }
    
    void doServe(const Rest::Request& req, Http::ResponseWriter res) {
		Http::serveFile(res, "public/index.html");
	}
}

這邊補充一下常用到的幾個方法:

如果要直接 serve 一個靜態檔案可以在 router 裡這樣寫:

Http::serveFile(res, "/path/to/file.txt");

如果要回傳內容可以這樣寫:

res.send(Http::Code::Ok, "Hello, World!");

如果要回傳 JSON 內容,可以先帶個表頭並回傳內容:

std::string json_output = "{"test": true}";
res.headers().add<Http::Header::ContentType>(MIME(Text, Json));
res.send(Http::Code::Ok, json_output);

這樣就可以用 C++ 寫出一個簡單的 server 了。

延伸閱讀