芽萌丸プログラミング部 @programming
@programming
2021/2/1 15:26 更新✏

C++でlibcurlを利用したHTTPクライアント

C++でlibcurlモジュールを利用したHTTPクライアントの簡単なサンプルです。

目次:

前提

サンプルコード

libcurlを使ったHTTPクライアント

libcurlを使ったシンプルなHTTPクライアントの自作クラスです。

Curl.h

#ifndef CURL_H
#define CURL_H

#include <curl/curl.h>

using namespace std;

/** A definition of the function pointer for http callback */
typedef void (*CurlCallback)(string err, string body);

/**
 * Curl HTTP client class
 *
 * Usage:
 * ```c++
 * void callback(string err, string body)
 * {
 *    if (err != "") {
 *      std::cerr << "Error:" << err << std::endl;
 *    } else {
 *      std::cout << body << std::endl;
 *    }
 * }
 *
 * Curl* curl = new Curl();
 * curl.get("https://www.google.com/", callback);
 * ```
 *
 * or use lambda function:
 *
 * ```c++
 * Curl* curl = new Curl();
 * curl->get(url, [](string err, string body) {
 *      if (err != "") {
 *     cerr << "ERROR: " << err << endl;
 *   } else {
 *     cout << body << endl;
 *   }
 * });
 *    ```
 */
class Curl {
private:

    /** response body */
    string body;

    // TIP: CURLOPT_WRITEFUNCTION では 引数となる関数に static しか受け付けないので強引に static cast しています:
    // see: https://curl.se/docs/faq.html#Using_C_non_static_functions_f
    static size_t invoke_write_data(char *buffer, size_t size, size_t nmemb, void *f) {
        // Call non-static member function.
        return static_cast<Curl*>(f)->write_data(buffer, size, nmemb, f);
    }

    /** a callback function for libcurl request */
    size_t write_data(char *buffer, size_t size, size_t nmemb, void *f) {
        int dataLength = size * nmemb;
        this->body.append(buffer, dataLength);
        return dataLength;
    }

public:

    /** user-agent */
    string useragent = "libcurl-agent/1.0";
    /** timeout */
    int timeout = 30L; // timeout 30 seconds

    /**
     * Constructor
     */
    Curl() {
        //
    }

    /**
     * HTTP GET
     */
    void get(const string url, const CurlCallback cb) {
        CURL* curl;
        CURLcode ret;

        this->body = ""; // init result body.
        string err = "";

        curl_global_init(CURL_GLOBAL_ALL);
        curl = curl_easy_init();

        if (curl == NULL) {
            err = "curl_easy_init() failed on " + url;
            return cb(err, "");
        }

        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, this->invoke_write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, this->useragent.c_str()); // UA
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, this->timeout); // timeout
        // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // verbose
        ret = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        curl_global_cleanup();

        if (ret != CURLE_OK) {
            err = "curl_easy_perform() failed on " + url + " (ret:" + to_string(ret) + ")";
            return cb(err, "");
        }
        return cb(err, this->body);
    }

    /**
     * HTTP POST
     */
    void post(const string url, const string data, const CurlCallback cb) {
        CURL* curl;
        CURLcode ret;

        this->body = ""; // init result body.
        string err = "";

        curl_global_init(CURL_GLOBAL_ALL);
        curl = curl_easy_init();

        if (curl == NULL) {
            err = "curl_easy_init() failed on " + url;
            return cb(err, "");
        }

        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, this->invoke_write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, this->useragent.c_str()); // UA
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, this->timeout); // timeout
        // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // verbose
        ret = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        curl_global_cleanup();

        if (ret != CURLE_OK) {
            err = "curl_easy_perform() failed on " + url + " (ret:" + to_string(ret) + ")";
            return cb(err, "");
        }
        return cb(err, this->body);
    }

};

#endif // CURL_H

libcurlモジュールのcurl_*なAPIの使い方はなんとなく理解できるかと思います。(少々汚いコードですがお許しを🙇)

mainコード

先ほどのCurlクラス(Curl.h)を使うmainコードです。

curl-test.cpp

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

using namespace std;

int main(int argc, char* argv[]) {

    string url = "http://127.0.0.1:3000/";

    Curl* curl = new Curl();

    // HTTP GET
    curl->get(url, [](string err, string body) {
        if (err != "") {
            cerr << "ERROR: " << err << endl;
        } else {
            cout << "GET Response body:" << endl;
            cout << body << endl;
        }
    });

    // HTTP POST
    string data = "a=123&b=456"; // posting data
    curl->post(url, data, [](string err, string body) {
        if (err != "") {
            cerr << "ERROR: " << err << endl;
        } else {
            cout << "POST Response body:" << endl;
            cout << body << endl;
        }
    });
}

テスト用Webサーバ (Node.js)

Node.jsで書かれた簡単なテスト用Webサーバです。 GETされるとクライアントへ "Hello, World!" を返し、POSTされるとPOSTされたデータを返すだけの簡単なサーバです。(テスト用なのでテキトーです。)

server.js

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  console.log("req.method:", req.method);

  if (req.method === "GET") {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello, World!\n');
  } else if (req.method === "POST") {
    let data = '';
    req.on('data', chunk => {
      data += chunk;
    });
    req.on('end', () => {
      console.log("post body:", data);
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end('post received: ' + data);
    });
  }
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

上記Webサーバを起動:

$ node server.js

Server running at http://127.0.0.1:3000/

サンプルコードのコンパイル&実行

mainコードのコンパイル&実行: (libcurlを使うために-lcurlオプション、もしくは$(pkg-config --libs libcurl)を付けてコンパイルします)

## コンパイル
$ g++ -o a.out curl-test.cpp $(pkg-config --libs libcurl) -std=c++11

## 実行:
$ ./a.out

GET Response body:
Hello, World!

POST Response body:
post received: a=123&b=456

Webサーバ側のログ:

Server running at http://127.0.0.1:3000/
req.method: GET
req.method: POST
post body: a=123&b=456

以上です。

参考

C++

芽萌丸プログラミング部 @programming
芽萌丸プログラミング部@programming
プログラミング関連アカウント。Web標準技術を中心に書いていきます。フロントエンドからサーバサイドまで JavaScript だけで済ませたい人たちの集いです。記事は主に @TanakaSoftwareLab が担当。
オススメ:Zattoyomiで時事ネタチェックの時間節約!