C言語によるHTTPクライアントの開発:libcurlとlibcjsonを活用したPOST送信実装

ビルド環境の準備

Debian系OSを基盤とし、ネットワーク通信とJSON処理に必要な開発パッケージを導入します。

# キャッシュ更新
sudo apt update
# コアビルドツールチェーンのインストール
sudo apt install build-essential
# コンパイラバージョン確認
gcc --version
# OpenSSL版libcurl開発ファイルの状態確認
apt list --installed 2>/dev/null | grep libcurl4-openssl-dev
# JSONパーサー用ライブラリの追加
sudo apt install libcjson-dev

検証用エンドポイント作成

Cコードとの疎通確認のため、Python製軽量サーバーを準備します。受信したペイロードをそのまま返却する構造にしています。

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/echo', methods=['POST'])
def echo_handler():
    received = request.get_json(silent=True) or {}
    
    response_payload = {
        "code": 200,
        "acknowledge": True,
        "captured_input": received.get("user_id", "N/A")
    }
    return jsonify(response_payload), 200

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=9900)

クライアント実装(C言語)

以下のコードでは、コールバック関数内でのメモリ再確保を構造体経由で処理し、エラー経路におけるリソース解放を一元化しています。変数名および関数分割は保守性を考慮して変更しています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <cjson/cJSON.h>

/* レスポンス蓄積用メモリ構造体 */
typedef struct {
    char *data;
    size_t size;
} NetBuffer;

/* libcurl通信終了毎のデータ取得フック */
size_t stream_handler(void *contents, size_t size, size_t count, void *ctx) {
    NetBuffer *buf = (NetBuffer *)ctx;
    size_t block_size = size * count;
    
    char *temp_ptr = realloc(buf->data, buf->size + block_size + 1);
    if (!temp_ptr) {
        fprintf(stderr, "ヒープ領域枯渇\n");
        return 0;
    }
    buf->data = temp_ptr;
    memcpy(&buf->data[buf->size], contents, block_size);
    buf->size += block_size;
    buf->data[buf->size] = '\0';
    return block_size;
}

int dispatch_post_request(const char *endpoint, const char *payload_str) {
    CURL *session = NULL;
    CURLcode transport_status;
    long response_code = 0;
    NetBuffer response_buf = {NULL, 0};
    
    curl_global_init(CURL_GLOBAL_ALL);
    session = curl_easy_init();
    if (!session) {
        fprintf(stderr, "CURLハンドラ生成不可\n");
        return -1;
    }

    /* 通信パラメータ設定 */
    curl_easy_setopt(session, CURLOPT_URL, endpoint);
    curl_easy_setopt(session, CURLOPT_POST, 1L);
    curl_easy_setopt(session, CURLOPT_POSTFIELDS, payload_str);
    
    /* カスタムヘッダー付与 */
    struct curl_slist *custom_headers = NULL;
    custom_headers = curl_slist_append(custom_headers, "Content-Type: application/json");
    custom_headers = curl_slist_append(custom_headers, "Accept: application/json");
    curl_easy_setopt(session, CURLOPT_HTTPHEADER, custom_headers);

    /* 受信バッファ紐付け */
    curl_easy_setopt(session, CURLOPT_WRITEFUNCTION, stream_handler);
    curl_easy_setopt(session, CURLOPT_WRITEDATA, &response_buf);

    /* クエリ発行 */
    transport_status = curl_easy_perform(session);
    if (transport_status != CURLE_OK) {
        fprintf(stderr, "転送異常: %s\n", curl_easy_strerror(transport_status));
        goto release_resources;
    }

    /* ステータスコード抽出 */
    curl_easy_getinfo(session, CURLINFO_RESPONSE_CODE, &response_code);
    printf("[NETWORK] 応答ステータス: %ld\n", response_code);
    printf("[RAW_DATA]\n%s\n\n", response_buf.data);

    /* JSON構造体への変換 */
    cJSON *tree = cJSON_ParseWithLength(response_buf.data, response_buf.size);
    if (tree) {
        cJSON *flag_node = cJSON_GetObjectItemCaseSensitive(tree, "acknowledge");
        if (cJSON_IsBool(flag_node)) {
            printf("[PARSER] ackフラグ判定: %s\n", flag_node->valueboolean ? "true" : "false");
        }
        cJSON_Delete(tree);
    } else {
        fprintf(stderr, "[ERROR] JSON構文解析失敗\n");
    }

release_resources:
    curl_easy_cleanup(session);
    curl_slist_free_all(custom_headers);
    free(response_buf.data);
    curl_global_cleanup();
    return (int)response_code;
}

int main(void) {
    const char *target = "http://127.0.0.1:9900/api/echo";
    const char *body = "{\"user_id\": \"c_impl_001\", \"action\": \"verify\"}";

    int exit_code = dispatch_post_request(target, body);
    return exit_code;
}

コンパイル手順

リンカーオプションに両方のライブラリを指定し、警告レベルを厳格に設定してオブジェクトを生成します。

gcc -std=c99 -O2 -Wall -Wextra -o net_client post_example.c -lcurl -lcjson

動作検証

バックグラウンドでPythonサーバーを稼働させた状態で、コンパイル済みのバイナリを実行します。

# サーバー起動(別セッション)
$ python3 http_server.py

# クライアント実行(メインセッション)
$ ./net_client
[NETWORK] 応答ステータス: 200
[RAW_DATA]
{"acknowledge": true, "captured_input": "c_impl_001", "code": 200}

[PARSER] ackフラグ判定: true

タグ: libcurl cJSON C11標準 CURLハンドリング JSONパース

8月28日 06:18 投稿