libcurlは、curlコマンドのようなHTTP等の通信をC/C++のプログラムから実行できるライブラリである。curlコマンドと同様に、HTTPやHTTPS以外にもFTP、FTPS、SFTP、SCPなど様々なプロトコルに対応している1。
libcurlを使用したプログラムをコンパイル・リンクする場合、gcc、g++やclang、clang++ではオプションで-lcurlをつける。
libcurlにはeasyインターフェース2とmultiインターフェース3の2つがあるが、この記事では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インターフェースで通信する例は以下のようになる。
#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
オプションを使うのが簡単。
$ curl -s --libcurl example.c https://example.com
上のコマンドを実行すると、以下のような内容で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