# Basic Client - cpp-httplib

# Basic Client

cpp-httplibはサーバーだけでなく、HTTPクライアント機能も備えています。`httplib::Client` を使って、GETやPOSTリクエストを送ってみましょう。

## テスト用サーバーの準備

クライアントの動作を確認するために、リクエストを受け付けるサーバーを用意します。次のコードを保存し、前章と同じ手順でコンパイル・実行してください。サーバーの詳しい解説は次章で行います。

    #include "httplib.h"
    #include <iostream>

    int main() {
        httplib::Server svr;

        svr.Get("/hi", [](const auto &, auto &res) {
            res.set_content("Hello!", "text/plain");
        });

        svr.Get("/search", [](const auto &req, auto &res) {
            auto q = req.get_param_value("q");
            res.set_content("Query: " + q, "text/plain");
        });

        svr.Post("/post", [](const auto &req, auto &res) {
            res.set_content(req.body, "text/plain");
        });

        svr.Post("/submit", [](const auto &req, auto &res) {
            std::string result;
            for (auto &[key, val] : req.params) {
                result += key + " = " + val + "\n";
            }
            res.set_content(result, "text/plain");
        });

        svr.Post("/upload", [](const auto &req, auto &res) {
            auto f = req.form.get_file("file");
            auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
            res.set_content(content, "text/plain");
        });

        svr.Get("/users/:id", [](const auto &req, auto &res) {
            auto id = req.path_params.at("id");
            res.set_content("User ID: " + id, "text/plain");
        });

        svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
            auto id = req.matches[1];
            res.set_content("File ID: " + std::string(id), "text/plain");
        });

        std::cout << "Listening on port 8080..." << std::endl;
        svr.listen("0.0.0.0", 8080);
    }

    #include "httplib.h"
    #include <iostream>

    int main() {
        httplib::Server svr;

        svr.Get("/hi", [](const auto &, auto &res) {
            res.set_content("Hello!", "text/plain");
        });

        svr.Get("/search", [](const auto &req, auto &res) {
            auto q = req.get_param_value("q");
            res.set_content("Query: " + q, "text/plain");
        });

        svr.Post("/post", [](const auto &req, auto &res) {
            res.set_content(req.body, "text/plain");
        });

        svr.Post("/submit", [](const auto &req, auto &res) {
            std::string result;
            for (auto &[key, val] : req.params) {
                result += key + " = " + val + "\n";
            }
            res.set_content(result, "text/plain");
        });

        svr.Post("/upload", [](const auto &req, auto &res) {
            auto f = req.form.get_file("file");
            auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
            res.set_content(content, "text/plain");
        });

        svr.Get("/users/:id", [](const auto &req, auto &res) {
            auto id = req.path_params.at("id");
            res.set_content("User ID: " + id, "text/plain");
        });

        svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
            auto id = req.matches[1];
            res.set_content("File ID: " + std::string(id), "text/plain");
        });

        std::cout << "Listening on port 8080..." << std::endl;
        svr.listen("0.0.0.0", 8080);
    }

## GETリクエスト

サーバーが起動したら、別のターミナルを開いて試してみましょう。まず、最もシンプルなGETリクエストです。

    #include "httplib.h"
    #include <iostream>

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

        auto res = cli.Get("/hi");
        if (res) {
            std::cout << res->status << std::endl;  // 200
            std::cout << res->body << std::endl;    // Hello!
        }
    }

    #include "httplib.h"
    #include <iostream>

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

        auto res = cli.Get("/hi");
        if (res) {
            std::cout << res->status << std::endl;  // 200
            std::cout << res->body << std::endl;    // Hello!
        }
    }

`httplib::Client` のコンストラクターにサーバーのアドレスを渡し、`Get()` でリクエストを送ります。戻り値の `res` からステータスコードやボディを取得できます。

対応する `curl` コマンドはこうなります。

    curl http://localhost:8080/hi
    # Hello!

    curl http://localhost:8080/hi
    # Hello!

## レスポンスの確認

レスポンスには、ステータスコードとボディ以外にもヘッダー情報が含まれています。

    auto res = cli.Get("/hi");
    if (res) {
        // ステータスコード
        std::cout << res->status << std::endl;  // 200

        // ボディ
        std::cout << res->body << std::endl;  // Hello!

        // ヘッダー
        std::cout << res->get_header_value("Content-Type") << std::endl;  // text/plain
    }

    auto res = cli.Get("/hi");
    if (res) {
        // ステータスコード
        std::cout << res->status << std::endl;  // 200

        // ボディ
        std::cout << res->body << std::endl;  // Hello!

        // ヘッダー
        std::cout << res->get_header_value("Content-Type") << std::endl;  // text/plain
    }

`res->body` は `std::string` なので、JSON レスポンスをパースしたい場合は nlohmann/json などの JSON ライブラリにそのまま渡せます。

## クエリパラメーター

GETリクエストにクエリパラメーターを付けるには、URLに直接書くか、`httplib::Params` を使います。

    auto res = cli.Get("/search", httplib::Params{{"q", "cpp-httplib"}});
    if (res) {
        std::cout << res->body << std::endl;  // Query: cpp-httplib
    }

    auto res = cli.Get("/search", httplib::Params{{"q", "cpp-httplib"}});
    if (res) {
        std::cout << res->body << std::endl;  // Query: cpp-httplib
    }

`httplib::Params` を使うと、特殊文字のURLエンコードを自動で行ってくれます。

    curl "http://localhost:8080/search?q=cpp-httplib"
    # Query: cpp-httplib

    curl "http://localhost:8080/search?q=cpp-httplib"
    # Query: cpp-httplib

## パスパラメーター

URLのパスに値を直接埋め込む場合も、クライアント側は特別なAPIは不要です。パスをそのまま `Get()` に渡すだけです。

    auto res = cli.Get("/users/42");
    if (res) {
        std::cout << res->body << std::endl;  // User ID: 42
    }

    auto res = cli.Get("/users/42");
    if (res) {
        std::cout << res->body << std::endl;  // User ID: 42
    }

    curl http://localhost:8080/users/42
    # User ID: 42

    curl http://localhost:8080/users/42
    # User ID: 42

テスト用サーバーには、正規表現でIDを数字のみに絞った `/files/(\d+)` もあります。

    auto res = cli.Get("/files/42");
    if (res) {
        std::cout << res->body << std::endl;  // File ID: 42
    }

    auto res = cli.Get("/files/42");
    if (res) {
        std::cout << res->body << std::endl;  // File ID: 42
    }

    curl http://localhost:8080/files/42
    # File ID: 42

    curl http://localhost:8080/files/42
    # File ID: 42

`/files/abc` のように数字以外を渡すと404が返ります。仕組みは次章で解説します。

## リクエストヘッダー

カスタムHTTPヘッダーを付けるには、`httplib::Headers` を渡します。`Get()` や `Post()` のどちらでも使えます。

    auto res = cli.Get("/hi", httplib::Headers{
        {"Authorization", "Bearer my-token"}
    });

    auto res = cli.Get("/hi", httplib::Headers{
        {"Authorization", "Bearer my-token"}
    });

    curl -H "Authorization: Bearer my-token" http://localhost:8080/hi

    curl -H "Authorization: Bearer my-token" http://localhost:8080/hi

## POSTリクエスト

テキストデータをPOSTしてみましょう。`Post()` の第2引数にボディ、第3引数にContent-Typeを指定します。

    auto res = cli.Post("/post", "Hello, Server!", "text/plain");
    if (res) {
        std::cout << res->status << std::endl;  // 200
        std::cout << res->body << std::endl;    // Hello, Server!
    }

    auto res = cli.Post("/post", "Hello, Server!", "text/plain");
    if (res) {
        std::cout << res->status << std::endl;  // 200
        std::cout << res->body << std::endl;    // Hello, Server!
    }

テスト用サーバーの `/post` はボディをそのまま返すので、送った文字列がそのまま返ってきます。

    curl -X POST -H "Content-Type: text/plain" -d "Hello, Server!" http://localhost:8080/post
    # Hello, Server!

    curl -X POST -H "Content-Type: text/plain" -d "Hello, Server!" http://localhost:8080/post
    # Hello, Server!

## フォームデータの送信

HTMLフォームのように、キーと値のペアを送ることもできます。`httplib::Params` を使います。

    auto res = cli.Post("/submit", httplib::Params{
        {"name", "Alice"},
        {"age", "30"}
    });
    if (res) {
        std::cout << res->body << std::endl;
        // age = 30
        // name = Alice
    }

    auto res = cli.Post("/submit", httplib::Params{
        {"name", "Alice"},
        {"age", "30"}
    });
    if (res) {
        std::cout << res->body << std::endl;
        // age = 30
        // name = Alice
    }

これは `application/x-www-form-urlencoded` 形式で送信されます。

    curl -X POST -d "name=Alice&age=30" http://localhost:8080/submit

    curl -X POST -d "name=Alice&age=30" http://localhost:8080/submit

## ファイルのPOST

ファイルをアップロードするには、`httplib::UploadFormDataItems` を使ってマルチパートフォームデータとして送信します。

    auto res = cli.Post("/upload", httplib::UploadFormDataItems{
        {"file", "Hello, File!", "hello.txt", "text/plain"}
    });
    if (res) {
        std::cout << res->body << std::endl;  // hello.txt (12 bytes)
    }

    auto res = cli.Post("/upload", httplib::UploadFormDataItems{
        {"file", "Hello, File!", "hello.txt", "text/plain"}
    });
    if (res) {
        std::cout << res->body << std::endl;  // hello.txt (12 bytes)
    }

`UploadFormDataItems` の各要素は `{name, content, filename, content_type}` の4つのフィールドで構成されます。

    curl -F "file=Hello, File!;filename=hello.txt;type=text/plain" http://localhost:8080/upload

    curl -F "file=Hello, File!;filename=hello.txt;type=text/plain" http://localhost:8080/upload

## エラーハンドリング

ネットワーク通信では、サーバーに接続できない場合があります。`res` が有効かどうかを必ず確認しましょう。

    httplib::Client cli("http://localhost:9999");  // 存在しないポート
    auto res = cli.Get("/hi");

    if (!res) {
        // 接続エラー
        std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
        // Error: Connection
        return 1;
    }

    // ここに到達すればレスポンスを受信できている
    if (res->status != 200) {
        std::cout << "HTTP Error: " << res->status << std::endl;
        return 1;
    }

    std::cout << res->body << std::endl;

    httplib::Client cli("http://localhost:9999");  // 存在しないポート
    auto res = cli.Get("/hi");

    if (!res) {
        // 接続エラー
        std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
        // Error: Connection
        return 1;
    }

    // ここに到達すればレスポンスを受信できている
    if (res->status != 200) {
        std::cout << "HTTP Error: " << res->status << std::endl;
        return 1;
    }

    std::cout << res->body << std::endl;

エラーには2つのレベルがあります。

- 接続エラー: サーバーに到達できなかった場合。`res` が偽になり、`res.error()` でエラーの種類を取得できます
- HTTPエラー: サーバーからエラーステータス（404、500など）が返ってきた場合。`res` は真ですが、`res->status` を確認する必要があります

## 次のステップ

クライアントからリクエストを送る方法がわかりました。次は、サーバー側をもっと詳しく見てみましょう。ルーティングやパスパラメータなど、サーバーの機能を掘り下げます。

次: Basic Server