1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RCC (立命館コンピュータークラブ)Advent Calendar 2024

Day 20

C++でもHTTPクライアント側の通信実装が可能だったので試してみる

Last updated at Posted at 2024-12-20

本稿ではC++でHTTP通信を行い, 取得したデータをユーザーに表示する方法について解説する.
docker-composeとGoのWebフレームワークであるEchoを用いてHTTPサーバーを起動した例は以前の記事で紹介したことがあると思う. 今回はGo言語のサーバーを実装し, C++側はVisual Studioを用いてlibcurlをリンクし, GETおよびPOSTリクエストを送信してレスポンスを表示するまでの流れを示す.

完成目標

今回は, "/"エンドポイントにアクセスすると"Hello, World"を返し, "/json"エンドポイントにアクセスすると{"message":"Hello from Echo server"}というJSONレスポンスを返すサーバーを実装する。
さらに, "/json"エンドポイントへPOSTリクエストを送った場合は, 受け取ったJSONデータを加工してレスポンスを返す実装例を示す.
docker-composeを用いてこのEchoサーバーをコンテナ上で起動し, クライアント側でlibcurlを用いてGETとPOSTを試してみる.

1.Echoを用いたGoサーバーの実装例

Echoフレームワークは

go get github.com/labstack/echo/v4
go mod tidy

で取得可能である。

server.go
package main

import (
    "encoding/json"
    "net/http"
    "github.com/labstack/echo/v4"
    "io"
)

type RequestData struct {
    Data string `json:"data"`
}

type Response struct {
    Message string `json:"message"`
}

func main() {
    e := echo.New()
    
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Hello, World")
    })
    
    // GET時は{"message":"Hello from Echo server"}を返す
    // POST時は受け取った{"data":"..."}を元に{"message":"Received: ..."}と返す
    e.Any("/json", func(c echo.Context) error {
        if c.Request().Method == http.MethodGet {
            res := Response{Message: "Hello from Echo server"}
            return c.JSON(http.StatusOK, res)
        } else if c.Request().Method == http.MethodPost {
            body, err := io.ReadAll(c.Request().Body)
            if err != nil {
                return c.JSON(http.StatusBadRequest, Response{Message: "Failed to read request"})
            }
            var reqData RequestData
            if err := json.Unmarshal(body, &reqData); err != nil {
                return c.JSON(http.StatusBadRequest, Response{Message: "Invalid JSON"})
            }
            res := Response{Message: "Received: " + reqData.Data}
            return c.JSON(http.StatusOK, res)
        } else {
            return c.JSON(http.StatusMethodNotAllowed, Response{Message: "Method not allowed"})
        }
    })
    
    e.Logger.Fatal(e.Start(":8080"))
}

上記ではGET時とPOST時でレスポンスを分けている。GETで/jsonへアクセスすると{"message":"Hello from Echo server"}が返り, POSTで{"data":"test"}を送れば{"message":"Received: test"}というレスポンスを返す。

2.Dockerfileとdocker-compose.yml

docker-composeでGo+Echoのコンテナを起動するため, 以下のファイルを用意する。
Dockerfile
goのバージョンは適宜変更してください

FROM golang:1.23-alpine 
WORKDIR /app
COPY . .
RUN go mod tidy && go build -o server server.go
EXPOSE 8080
CMD ["./server"]

docker-compose.yml

version: '3'
services:
  go_server:
    build: .
    ports:
      - "8080:8080"

サーバー側の実行イメージとして,Dockerコンテナを起動したら http://localhost:8080 および http://localhost:8080/json へアクセス可能となり,curlなどで確認すると, /エンドポイントではHello, World, /jsonエンドポイントではGET時{"message":"Hello from Echo server"}, POST時に{"data":"test"}を送れば{"message":"Received: test"}が返る。

3.Visual StudioでのlibcurlセットアップとC++クライアントからのHTTP通信

windowsマシンにvcpkgを使ってlibcurlをインストールする.コマンドプロンプトに以下のコマンドを入れる

git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg.exe install curl:x64-windows

インストールが成功すると,以下のディレクトリに curl ライブラリがインストールされる.
ライブラリファイル

C:\vcpkg\installed\x64-windows\lib\libcurl.lib

ヘッダーファイル

C:\vcpkg\installed\x64-windows\include\curl\curl.h

インストール後、Visual Studio プロジェクトに以下を設定する

「プロジェクトを右クリック > プロパティ > 構成プロパティ > C/C++ > 全般」 に移動。
「追加のインクルードディレクトリ」 に次を設定:

C:\vcpkg\installed\x64-windows\include

「構成プロパティ > リンカー > 全般」 に移動。 「追加のライブラリディレクトリ」 に次を設定:

C:\vcpkg\installed\x64-windows\lib

「構成プロパティ > リンカー > 入力」 に移動。 「追加の依存ファイル」 に次を設定:

libcurl.lib

プロジェクトにdllファイルを移動する必要もあるのでコマンドプロンプトで移動させる.

copy C:\vcpkg\installed\x64-windows\bin\libcurl.dll C:\Path\To\Your\Project\x64\Debug

他のdllファイルも見つけられないようなエラーが出たら同様にそのファイルもcopyコマンドを実行すること

4.GETリクエスト例(client.cpp)

以下はhttp://localhost:8080/jsonへGETリクエストを送り, レスポンスを表示するコード例である。
docker-composeでEchoサーバーが起動している状態で実行すると, {"message":"Hello from Echo server"}が表示される。

client.cpp
#include <iostream>
#include <string>
#include "curl/curl.h"

size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
    size_t totalSize = size * nmemb;
    std::string* str = static_cast<std::string*>(userp);
    str->append(static_cast<char*>(contents), totalSize);
    return totalSize;
}

int main() {
    const std::string url = "http://localhost:8080/json";
    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to init curl" << std::endl;
        return 1;
    }

    std::string responseBuffer;
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer);

    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    } else {
        std::cout << "Response from server:" << std::endl;
        std::cout << responseBuffer << std::endl;
    }

    curl_easy_cleanup(curl);
    return 0;
}

5.POSTリクエスト例(client_post.cpp)

次にPOSTリクエストを送る例である。
以下はhttp://localhost:8080/json{"data":"test"}をPOSTで送り, {"message":"Received: test"}というレスポンスを取得する。

client_post.cpp
#include <iostream>
#include <string>
#include "curl/curl.h"

size_t WriteCallback2(void* contents, size_t size, size_t nmemb, void* userp) {
    size_t totalSize = size * nmemb;
    std::string* str = static_cast<std::string*>(userp);
    str->append(static_cast<char*>(contents), totalSize);
    return totalSize;
}

int main() {
    const std::string url = "http://localhost:8080/json";
    const std::string postData = "{\"data\":\"test\"}";

    CURL* curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to init curl" << std::endl;
        return 1;
    }

    std::string responseBuffer;
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_POST, 1L);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)postData.size());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback2);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer);

    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

    CURLcode res = curl_easy_perform(curl);
    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    } else {
        std::cout << "Response from server:" << std::endl;
        std::cout << responseBuffer << std::endl;
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    return 0;
}

この後にdocker-compose.ymlがあるディレクトリで以下のコマンドを実行してクライアント側のファイルを実行すればOK

docker compose up

wslで上記のコマンドを実行するにはdocker desktopとwslを連携させる必要がある

実行結果

client_post.cppの実行結果
スクリーンショット 2024-12-21 0.08.39.png
client.cppの実行結果
スクリーンショット 2024-12-21 0.09.16.png

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?