libcurlの使い方(easyインターフェース)

libcurlは、curlコマンドのようなHTTP等の通信をC/C++のプログラムから実行できるライブラリである。curlコマンドと同様に、HTTPやHTTPS以外にもFTP、FTPS、SFTP、SCPなど様々なプロトコルに対応している[^1]。
libcurlを使用したプログラムをコンパイル・リンクする場合、gcc、g++やclang、clang++ではオプションで-lcurlをつける。
libcurlにはeasyインターフェース[^2]とmultiインターフェース[^3]の２つがあるが、この記事ではeasyインターフェースについて説明していく。
# 1 使用する関数
easyインターフェースで使う関数は主に5つ。
## 1.1 curl_easy_init
[^4]
通信に使うハンドルを初期化し、取得する。
## 1.2 curl_easy_setopt
[^5]
ハンドルにオプションを設定する。通信先のURLも含め、ほとんどの設定はここで設定することになる。
たとえば、受け取ったデータを他で使いたい場合、CURLOPT_WRITEFUNCTIONでコールバック関数を設定する必要がある。
## 1.3 curl_easy_perform
[^6]
通信を実行する。通信が終了するまで後続の処理はブロックされる。
戻り値として```CURLcode```を返すので、これを使って通信が成功したかどうかを判定する。
## 1.4 curl_easy_getinfo
[^7]
実行した通信に関する情報を取得する。
## 1.5 curl_easy_cleanup
[^8]
ハンドルを終了する。
# 2 例
easyインターフェースで通信する例は以下のようになる。
```cpp:sample.cpp
#include <iostream>
#include <string>
extern "C"{
#include <curl/curl.h>
}

size_t WriteCallback(char* ptr, size_t size, size_t nmemb, std::string *responseData) {
    size_t totalSize = size * nmemb;
    responseData->append(ptr, totalSize);
    return totalSize;
}

int main(int argc, char* argv[]) {
    CURL* hnd;
    CURLcode res;
    std::string responseData;

    hnd = curl_easy_init();
    if(hnd) {
        curl_easy_setopt(hnd, CURLOPT_URL, "https://example.com");
        curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(hnd, CURLOPT_WRITEDATA, &responseData);

        curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);
        curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
        curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
        curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);
        curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
        curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.88.1");

        res = curl_easy_perform(hnd);

        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        } else {
            std::cout << "Response data: " << responseData << std::endl;
        }

        curl_easy_cleanup(hnd);
    }
    return 0;
}
```
## 2.1 コールバック関数
コールバック関数(上の例だと```WriteCallback```)は```curl_easy_setopt```を使って```CURLOPT_WRITEFUNCTION```[^9]に設定する。コールバック関数の第一引数には受け取ったデータのポインタが渡される。第二引数と第三引数を掛け算すると受け取ったデータのサイズになる。
第四引数は```curl_easy_setopt```で```CURLOPT_WRITEDATA```[^10]に指定したポインタが渡される。
コールバック関数は一回の通信でも複数回呼び出されることがあるので、一回しか呼び出されないと勘違いしてはいけない(上の例だとappend()で毎回responseDataに追記している)
## 2.2 設定するオプション
```curl_easy_setopt```で設定するオプションについては、curlコマンドで```--libcurl```オプションを使うのが簡単。
```bash
$ curl -s --libcurl example.c https://example.com
```
上のコマンドを実行すると、以下のような内容で```example.c```が作成される。
```c:example.c
/********* Sample code generated by the curl command line tool **********
 * All curl_easy_setopt() options are documented at:
 * https://curl.se/libcurl/c/curl_easy_setopt.html
 ************************************************************************/
#include <curl/curl.h>

int main(int argc, char *argv[])
{
  CURLcode ret;
  CURL *hnd;

  hnd = curl_easy_init();
  curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, 102400L);
  curl_easy_setopt(hnd, CURLOPT_URL, "https://example.coc");
  curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
  curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.88.1");
  curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L);
  curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS);
  curl_easy_setopt(hnd, CURLOPT_FTP_SKIP_PASV_IP, 1L);
  curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);

  /* Here is a list of options the curl code used that cannot get generated
     as source easily. You may choose to either not use them or implement
     them yourself.

  CURLOPT_WRITEDATA was set to an object pointer
  CURLOPT_INTERLEAVEDATA was set to an object pointer
  CURLOPT_WRITEFUNCTION was set to a function pointer
  CURLOPT_READDATA was set to an object pointer
  CURLOPT_READFUNCTION was set to a function pointer
  CURLOPT_SEEKDATA was set to an object pointer
  CURLOPT_SEEKFUNCTION was set to a function pointer
  CURLOPT_ERRORBUFFER was set to an object pointer
  CURLOPT_STDERR was set to an object pointer
  CURLOPT_HEADERFUNCTION was set to a function pointer
  CURLOPT_HEADERDATA was set to an object pointer

  */

  ret = curl_easy_perform(hnd);

  curl_easy_cleanup(hnd);
  hnd = NULL;

  return (int)ret;
}
/**** End of sample code ****/
```
このように、コールバック以外のオプションについては自動で生成してくれるので、そのまま使えば良い。
# 3 通信を複数回行う場合
通信を複数回行う場合、一度使用したハンドルを使いまわすほうがリソース的には効率的になる。
```curl_easy_setopt```で設定したオプションをデフォルトに戻したい場合は```curl_easy_reset```[^11]を使用する。
# 4 マルチスレッド
[^12]
マルチスレッド環境では、スレッド同士でハンドルを共有してはいけない。各スレッドで別々にスレッドを取得する。
libcurlをマルチスレッドで使用する場合、以前は```curl-global-init```をプログラム開始時に明示的に呼ぶ必要があった(例:[^13])が、libcurl 7.84.0以降では多くの環境で```curl-global-init```はスレッドセーフになっているよう(もちろん明示的に呼んでも問題ない)。
マルチスレッド環境の場合、```curl_easy_setopt```で```CURLOPT_NOSIGNAL```[^14]を1Lに設定する必要がある。

# 参考リンク
https://curl.se/libcurl/
https://curl.se/libcurl/c/libcurl-tutorial.html
https://everything.curl.dev/libcurl/index.html

[^1]: https://curl.se/libcurl/
[^2]: https://curl.se/libcurl/c/libcurl-easy.html
[^3]: https://curl.se/libcurl/c/libcurl-multi.html
[^4]: https://curl.se/libcurl/c/curl_easy_init.html
[^5]: https://curl.se/libcurl/c/curl_easy_setopt.html
[^6]: https://curl.se/libcurl/c/curl_easy_perform.html
[^7]: https://curl.se/libcurl/c/curl_easy_getinfo.html
[^8]: https://curl.se/libcurl/c/curl_easy_cleanup.html
[^9]: https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
[^10]: https://curl.se/libcurl/c/CURLOPT_WRITEDATA.html
[^11]: https://curl.se/libcurl/c/curl_easy_reset.html
[^12]: https://curl.se/libcurl/c/threadsafe.html
[^13]: https://blog.regrex.jp/2019/07/01/%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%81%A7%E3%80%8Clibcurl%E3%80%8D%E4%BD%BF%E7%94%A8%E6%99%82%E3%81%AE%E6%B3%A8%E6%84%8F%E7%82%B9/
[^14]: https://curl.se/libcurl/c/CURLOPT_NOSIGNAL.html