cpp-httplib v0.35.0

What's Next

Tourお疲れさまでした! cpp-httplibの基本はひと通り押さえましたね。でも、まだまだ便利な機能があります。Tourで取り上げなかった機能をカテゴリー別に紹介します。

Streaming API

LLMのストリーミング応答や大きなファイルのダウンロードでは、レスポンス全体をメモリに載せたくないですよね。stream::Get() を使えば、データをチャンクごとに処理できます。

httplib::Client cli("http://localhost:11434");

auto result = httplib::stream::Get(cli, "/api/generate");

if (result) {
    while (result.next()) {
        std::cout.write(result.data(), result.size());
    }
}
httplib::Client cli("http://localhost:11434");

auto result = httplib::stream::Get(cli, "/api/generate");

if (result) {
    while (result.next()) {
        std::cout.write(result.data(), result.size());
    }
}

Get()content_receiver コールバックを渡す方法もあります。こちらはKeep-Aliveと併用できます。

httplib::Client cli("http://localhost:8080");

cli.Get("/stream", [](const char *data, size_t len) {
    std::cout.write(data, len);
    return true;
});
httplib::Client cli("http://localhost:8080");

cli.Get("/stream", [](const char *data, size_t len) {
    std::cout.write(data, len);
    return true;
});

サーバー側には set_content_provider()set_chunked_content_provider() があります。サイズがわかっているなら前者、不明なら後者を使ってください。

// サイズ指定あり(Content-Length が設定される)
svr.Get("/file", [](const auto &, auto &res) {
    auto size = get_file_size("large.bin");
    res.set_content_provider(size, "application/octet-stream",
        [](size_t offset, size_t length, httplib::DataSink &sink) {
            // offset から length バイト分を送る
            return true;
        });
});

// サイズ不明(Chunked Transfer Encoding)
svr.Get("/stream", [](const auto &, auto &res) {
    res.set_chunked_content_provider("text/plain",
        [](size_t offset, httplib::DataSink &sink) {
            sink.write("chunk\n", 6);
            return true;  // falseを返すと終了
        });
});
// サイズ指定あり(Content-Length が設定される)
svr.Get("/file", [](const auto &, auto &res) {
    auto size = get_file_size("large.bin");
    res.set_content_provider(size, "application/octet-stream",
        [](size_t offset, size_t length, httplib::DataSink &sink) {
            // offset から length バイト分を送る
            return true;
        });
});

// サイズ不明(Chunked Transfer Encoding)
svr.Get("/stream", [](const auto &, auto &res) {
    res.set_chunked_content_provider("text/plain",
        [](size_t offset, httplib::DataSink &sink) {
            sink.write("chunk\n", 6);
            return true;  // falseを返すと終了
        });
});

大きなファイルのアップロードには make_file_provider() が便利です。ファイルを全部メモリに読み込まず、ストリーミングで送れます。

httplib::Client cli("http://localhost:8080");

auto res = cli.Post("/upload", {}, {
    httplib::make_file_provider("file", "/path/to/large-file.zip")
});
httplib::Client cli("http://localhost:8080");

auto res = cli.Post("/upload", {}, {
    httplib::make_file_provider("file", "/path/to/large-file.zip")
});

Server-Sent Events (SSE)

SSEクライアントも用意しています。自動再接続や Last-Event-ID による再開にも対応しています。

httplib::Client cli("http://localhost:8080");
httplib::sse::SSEClient sse(cli, "/events");

sse.on_message([](const httplib::sse::SSEMessage &msg) {
    std::cout << msg.event << ": " << msg.data << std::endl;
});

sse.start();  // ブロッキング、自動再接続あり
httplib::Client cli("http://localhost:8080");
httplib::sse::SSEClient sse(cli, "/events");

sse.on_message([](const httplib::sse::SSEMessage &msg) {
    std::cout << msg.event << ": " << msg.data << std::endl;
});

sse.start();  // ブロッキング、自動再接続あり

イベントタイプごとにハンドラーを分けることもできますよ。

sse.on_event("update", [](const httplib::sse::SSEMessage &msg) {
    // "update" イベントだけ処理
});
sse.on_event("update", [](const httplib::sse::SSEMessage &msg) {
    // "update" イベントだけ処理
});

認証

クライアントにはBasic認証、Bearer Token認証、Digest認証のヘルパーを用意しています。

httplib::Client cli("https://api.example.com");
cli.set_basic_auth("user", "password");
cli.set_bearer_token_auth("my-token");
httplib::Client cli("https://api.example.com");
cli.set_basic_auth("user", "password");
cli.set_bearer_token_auth("my-token");

圧縮

gzip、Brotli、Zstandardによる圧縮・展開に対応しています。使いたい方式のマクロを定義してコンパイルしましょう。

圧縮方式マクロ定義
gzipCPPHTTPLIB_ZLIB_SUPPORT
BrotliCPPHTTPLIB_BROTLI_SUPPORT
ZstandardCPPHTTPLIB_ZSTD_SUPPORT
httplib::Client cli("https://example.com");
cli.set_compress(true);    // リクエストボディを圧縮
cli.set_decompress(true);  // レスポンスボディを展開
httplib::Client cli("https://example.com");
cli.set_compress(true);    // リクエストボディを圧縮
cli.set_decompress(true);  // レスポンスボディを展開

プロキシ

HTTPプロキシ経由で接続できます。

httplib::Client cli("https://example.com");
cli.set_proxy("proxy.example.com", 8080);
cli.set_proxy_basic_auth("user", "password");
httplib::Client cli("https://example.com");
cli.set_proxy("proxy.example.com", 8080);
cli.set_proxy_basic_auth("user", "password");

タイムアウト

接続・読み取り・書き込みのタイムアウトを個別に設定できます。

httplib::Client cli("https://example.com");
cli.set_connection_timeout(5, 0);  // 5秒
cli.set_read_timeout(10, 0);       // 10秒
cli.set_write_timeout(10, 0);      // 10秒
httplib::Client cli("https://example.com");
cli.set_connection_timeout(5, 0);  // 5秒
cli.set_read_timeout(10, 0);       // 10秒
cli.set_write_timeout(10, 0);      // 10秒

Keep-Alive

同じサーバーに何度もリクエストするなら、Keep-Aliveを有効にしましょう。TCP接続を再利用するので効率的です。

httplib::Client cli("https://example.com");
cli.set_keep_alive(true);
httplib::Client cli("https://example.com");
cli.set_keep_alive(true);

サーバーのミドルウェア

リクエスト処理の前後にフックを挟めます。

svr.set_pre_routing_handler([](const auto &req, auto &res) {
    // すべてのリクエストの前に実行される
    return httplib::Server::HandlerResponse::Unhandled;  // 通常のルーティングに進む
});

svr.set_post_routing_handler([](const auto &req, auto &res) {
    // レスポンスが返された後に実行される
    res.set_header("X-Server", "cpp-httplib");
});
svr.set_pre_routing_handler([](const auto &req, auto &res) {
    // すべてのリクエストの前に実行される
    return httplib::Server::HandlerResponse::Unhandled;  // 通常のルーティングに進む
});

svr.set_post_routing_handler([](const auto &req, auto &res) {
    // レスポンスが返された後に実行される
    res.set_header("X-Server", "cpp-httplib");
});

req.user_data を使うと、ミドルウェアからハンドラーにデータを渡せます。認証トークンのデコード結果を共有するときに便利です。

svr.set_pre_routing_handler([](const auto &req, auto &res) {
    req.user_data["auth_user"] = std::string("alice");
    return httplib::Server::HandlerResponse::Unhandled;
});

svr.Get("/me", [](const auto &req, auto &res) {
    auto user = std::any_cast<std::string>(req.user_data.at("auth_user"));
    res.set_content("Hello, " + user, "text/plain");
});
svr.set_pre_routing_handler([](const auto &req, auto &res) {
    req.user_data["auth_user"] = std::string("alice");
    return httplib::Server::HandlerResponse::Unhandled;
});

svr.Get("/me", [](const auto &req, auto &res) {
    auto user = std::any_cast<std::string>(req.user_data.at("auth_user"));
    res.set_content("Hello, " + user, "text/plain");
});

エラーや例外のハンドラーもカスタマイズできますよ。

svr.set_error_handler([](const auto &req, auto &res) {
    res.set_content("Custom Error Page", "text/html");
});

svr.set_exception_handler([](const auto &req, auto &res, std::exception_ptr ep) {
    res.status = 500;
    res.set_content("Internal Server Error", "text/plain");
});
svr.set_error_handler([](const auto &req, auto &res) {
    res.set_content("Custom Error Page", "text/html");
});

svr.set_exception_handler([](const auto &req, auto &res, std::exception_ptr ep) {
    res.status = 500;
    res.set_content("Internal Server Error", "text/plain");
});

ロギング

サーバーでもクライアントでもロガーを設定できます。

svr.set_logger([](const auto &req, const auto &res) {
    std::cout << req.method << " " << req.path << " " << res.status << std::endl;
});
svr.set_logger([](const auto &req, const auto &res) {
    std::cout << req.method << " " << req.path << " " << res.status << std::endl;
});

Unix Domain Socket

TCP以外に、Unix Domain Socketでの通信にも対応しています。同じマシン上のプロセス間通信に使えます。

// サーバー
httplib::Server svr;
svr.set_address_family(AF_UNIX);
svr.listen("/tmp/httplib.sock", 0);
// サーバー
httplib::Server svr;
svr.set_address_family(AF_UNIX);
svr.listen("/tmp/httplib.sock", 0);
// クライアント
httplib::Client cli("http://localhost");
cli.set_address_family(AF_UNIX);
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});

auto res = cli.Get("/");
// クライアント
httplib::Client cli("http://localhost");
cli.set_address_family(AF_UNIX);
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});

auto res = cli.Get("/");

さらに詳しく

もっと詳しく知りたいときは、以下を参照してください。

  • Cookbook — よくあるユースケースのレシピ集
  • README — 全APIのリファレンス
  • README-sse — Server-Sent Eventsの使い方
  • README-stream — Streaming APIの使い方
  • README-websocket — WebSocketサーバーの使い方