2019年7月8日 更新
2021年6月24日 追記 今後の更新予定はありません。
#まえがき
業務で認証付きのWeb API (HTTPS)を叩いてバイナリ音声をPOST送信して結果を取得するコードをC++で書く必要が生じたのですが、最初に使おうとしたC++ REST SDKでは理想通りの挙動が得られず、curlラッパーライブラリのcurlppはVS2017(VC++14)では動かせずで、情報量の多そうなlibcurlに行き着きました。しかし、日本語情報でまとまったものが無かったので、公式英語チュートリアルを読んで「この情報が欲しかった」と思うことをまとめていくのが本記事になります。
*ビルド済みの前提で書きます。
*書きかけで随時更新します。
*上述した目的を果たすコードについても後に更新するつもりです。
URL:https://curl.haxx.se/libcurl/c/libcurl-tutorial.html
##Global Preparation
- サンプルコード
// 処理の先頭
curl_global_init(CURL_GLOBAL_ALL); // [1]
// ...
// なんらかのcurllibの関数を用いた処理
// ...
curl_global_cleanup(); // [2]
// 処理の末尾
// [1] 処理の先頭にプログラム中で一度だけ書く。引数は次のいずれか。
// - CURL_GLOBAL_ALL 次の2つを包含。(これ呼んでおけば間違いない)
// - CURL_GLOBAL_WIN32 Win上で実行するなら呼ぶ。
// - CURL_GLOBAL_SSL SSLを使うなら呼ぶ。
// 呼び出し忘れても、後述する `curl_easy_perform()`の呼び出し時に
// 検出してくれるが、当てにせず必ず書くこと。
// [2] 処理の末尾にプログラム中で一度だけ書く。
Handle the Easy libcurl
ハンドルとcurl_easy_setopt()
- 実行するセッションごとにハンドルのポインタを1つ作成する。
CURL *handle = curl_easy_init();
- ハンドルを指定してリクエストのオプションを指定していく。
-
curl_easy_setopt(handle, CURLOPT_......., 値);
- 値は文字列ポインタ
char*
かcurl_off_t
型(libcurlにおけるlong
型のマクロ定義)整数。- std::stringは
c.str()
して突っ込め。
- std::stringは
- 設定は再び設定するまで保持される。
- 値は文字列ポインタ
-
cur_easy_reset()
でハンドルのオプションをリセットできる。 -
curl_easy_duphandle()
でオプションの設定ごとハンドルを複製できる。- 単一の送信処理をするだけなら、多分この2つは使う機会はない。
-
受信データの処理方法
-
POSTして返ってきたデータを任意の場所へ書き出す方法について。
-
基本設定
// ...
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback); // [1]
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &data_pointer); // [2]
auto result = curl_easy_perform(handle); // [3]
// [1] write_callbackはコールバック関数を指定。 / コールバック関数の定義は後述。
// [2] 受信データの書き出し先を指定。コールバック関数の指定がない場合は標準出力される。
// ファイルに書き込む場合、書き込みモードのファイルポインタを指定する。
// [3] 接続開始する。通信が終了すると、通信の成否を表すコードを返してくる。
// `CURLOPT_ERRORBUFFER`を指定するとさらに読みやすいメッセージを格納してくれるらしい。
- データ受信時に繰り返し呼び出されるコールバック関数
// C++ではコールバック関数を static宣言するのが必須
// buf: 受信したデータ
// size, nmemb: サイズに関する情報
// user_struct: 任意のデータをコールバック関数側に渡すための構造体ポインタ
static size_t write_callback(char *buf, size_t size, size_t nmemb, void *user_struct)
{
// デバッグ出力の例(VS2017 with MFC Framework)
char str[2049];
sprintf_s(str, "Read %u bytes.\n%s\n", size*nitems, buf);
OutputDebugString(str);
// libcurlに返す最大バッファサイズ(符号なし整数型)を表し、必ずこれをreturn すること。
// アップロードが終了する時は0を返す。
return size * nmemb;
}
- 環境による注意事項
-
Win32ターゲットでビルドされた**ダイナミックリンク・ライブラリ(.dll)**では、
CURLOPT_WRITEDATA
の指定時にCURLOPT_WRITEFUNCTION
も指定しないとクラッシュする。
-
Win32ターゲットでビルドされた**ダイナミックリンク・ライブラリ(.dll)**では、
- この後も繰り返し通信する場合、特に理由のない限りハンドルを使い回すのが推奨されている。
When It Doesn't Work
動かない時はこうしろ的な節です。
このへん適当です。平易な英語なのでドキュメント読んでください。
-
CURLOPT_VERBOSE
を1にせよ。 -
CURLOPT_HEADER
を1にせよ。- レスポンスヘッダを出力に含むようになる。
-
CURL_DEBUGFUNCTION
でデバッグ向きのデータを吐ける。
Upload Data to a Remote Site
- この節には、データをアップロードするシチュエーションではどうするか、ということが書かれている。
- 基本設定
curl_global_init(CURL_GLOBAL_ALL);
CURL *handle = curl_easy_init();
curl_easy_setopt(handle, CURLOPT_URL, url); // [1]
curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_callback); // [2]
curl_easy_setopt(handle, CURLOPT_READDATA, &data_pointer); // [3]
curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L); // [4]
curl_easy_setopt(handle, CURLOPT_INFILESIZE, (curl_off_t)filesize); // [5]
auto result = curl_easy_perform(handle); // [6]
// ...
curl_global_cleanup();
// [1] アップロード先のURLを指定。
// [2] read_callbackはコールバック関数を指定。 / コールバック関数の定義は後述。
// [3] 読み出すデータのポインタを指定。 / もっぱら読み込みモードのファイルポインタ。
// [4] アップロード処理がしたいことをlibcurlに教える。
// [5] いくつかのプロトコルは正常動作のためにデータサイズを教える必要があり、指定。
// libcurl 7.10.3以降では、HTTPアップロードがサイズ未知のまま実行されると
// チャンク転送コーディング(Chunked Transfer Coding)に切り替わる。
// ただし、最初から大容量バイナリの送信を想定するなら、これではなく
// 後述するヘッダーの設定内でコーディング方式を指定するのが望ましい。
// curl_off_t型は libcurlでマクロ定義された long型を意味する。
// [6] これを呼び出したタイミングでアップロード処理が実行され、
// コールバック関数が呼ばれ、データが読み出されていく。
- アップロード時に繰り返し呼び出されるコールバック関数
// コールバック関数
// buf: 読み込んだデータ
// user_struct: 任意のデータをコールバック関数側に渡すための構造体ポインタ
size_t read_callback(char *buf, size_t size, size_t nitems, void *user_struct)
{
// デバッグ出力の例(VS2017 with MFC Framework)
char str[2049];
sprintf_s(str, "Read %u bytes.\n%s\n", size*nitems, buf);
OutputDebugString(str);
// libcurlに返す最大バッファサイズ(符号なし整数型)を表し、必ずこれをreturn すること。
// アップロードが終了する時は0を返す。
return size * nitems;
}
HTTP Posting
- POSTでテキストや選択肢などのフォーム入力を送信する場合の説明は省略。
- マルチパートポストの記述も省略。
- ここではバイナリデータをPOST送信する場合の説明をまとめます。
- ヘッダー指定方法の説明を含みます。
CURL *handle = curl_easy_init();
struct curl_slist *headers = NULL; // [1]
headers = curl_list_append(headers, "Content-Type: audio/wav"); // [2]
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, data_pointer); // [3]
curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, (curl_off_t)datasize); // [4]
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); // [5]
curl_easy_perform(handle);
curl_slist_free_all(headers); // [6]
// [1] ヘッダーを格納するリストである curl_slist構造体ポインタを作る。
// [2] curl_list_append()で任意のヘッダーを指定する。
// [3] POST送信するバイナリデータのポインタを指定する。
// [4] POSTによるバイナリ送信ではlibcurlがstrlen()を使用できないため、
// 送信データサイズを教える必要がある。
// [5] 送信するヘッダーリストを指定する。
// [6] 使用しなくなったヘッダーリストを破棄する。
Showing Progress
// ...
curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, progress_callback); [1]
// ...
// [1] 通信時に呼び出すプログレスについてのコールバック関数を指定する。
HTTP Headers Used by libcurl
- HTTPリクエスト時に自動で渡されるヘッダがある。もちろん書き換えや削除はOK。
- Host
- HTTP1.1と多くのHTTP1.0サーバが要求するヘッダー。接続相手のサーバ名にあたる。
- Accept: "/"
- 任意のメディアタイプのデータを受け取れることを意味する。
- Expect: 100-Continue
- これによって、POSTリクエストの実行時、libcurlはデータ送信前にまず「OK」メッセージをサーバに要求する。
- データ量がlibcurlによって「少ない」と見なされた時、libcurlはこのヘッダーを使用しない。
- Host
今後まとめる予定の節
Showing Progress
こちらの公式リファレンスサイト( https://ec.haxx.se/callback-progress.html )を読む限り、本チュートリアルのProgressまわりの情報は古いようです。
せっかくまとめましたが、追記するか記事を改めるかして情報を整理していきたいと思っています。
その他の節は?
自分が使わないものをまとめるモチベーションはないので、現状まとめる予定はありません。
サンプルコードは?
なんとcurlコマンドで--libcurl
オプションを指定すると、不完全ですが挙動を再現するソースを吐いてくれます(エーッ)。基本的な部分はこの通りにして必要に応じて書き換えればいいと思いますが、コールバック関数などの絡む部分は自動生成してくれませんので、がんばって書きましょう。
これ読んで知りました→ https://takuya-1st.hatenablog.jp/entry/2015/06/25/172521
次はGET www.google.com
する例です。
curl google.com --libcurl google.c
* All curl_easy_setopt() options are documented at:
* https://curl.haxx.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_URL, "www.google.com");
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.54.0");
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);
/* Here is a list of options the curl code used that cannot get generated
as source easily. You may select to either not use them or implement
them yourself.
CURLOPT_WRITEDATA set to a objectpointer
CURLOPT_INTERLEAVEDATA set to a objectpointer
CURLOPT_WRITEFUNCTION set to a functionpointer
CURLOPT_READDATA set to a objectpointer
CURLOPT_READFUNCTION set to a functionpointer
CURLOPT_SEEKDATA set to a objectpointer
CURLOPT_SEEKFUNCTION set to a functionpointer
CURLOPT_ERRORBUFFER set to a objectpointer
CURLOPT_STDERR set to a objectpointer
CURLOPT_HEADERFUNCTION set to a functionpointer
CURLOPT_HEADERDATA set to a objectpointer
*/
ret = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
hnd = NULL;
return (int)ret;
}
/**** End of sample code ****/
- こちらは、コールバック関数を書く必要のあるものを除いて、curl --libcurl で生成されたコードを一部加工したものです。
> (認証付きのWeb API (HTTPS)を叩いてバイナリ音声をPOST送信して結果を取得する)
#include <curl/curl.h>
int main(int argc, char *argv[])
{
CURLcode ret;
CURL *hnd;
struct curl_slist *slist1;
slist1 = NULL;
slist1 = curl_slist_append(slist1, "Transfer-Encoding: chunked"); // [1]
slist1 = curl_slist_append(slist1, "Content-Type: audio/wav"); // [2]
slist1 = curl_slist_append(slist1, "Expect:"); // [3]
// [1] チャンク転送コーディングを指定。
// [2] 送信するMIME-Typeをwavオーディオに指定。
// [3] 一発目のリクエストですぐさまデータを流し込めるよう、OKメッセージを待機しないように指定。
hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_URL, "https://***.***.***/api/... ");
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); // [4]
curl_easy_setopt(hnd, CURLOPT_USERPWD, "username:password"); // [5]
curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, "RIFFd\225\031"); // [?]
curl_easy_setopt(hnd, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)1234567);
curl_easy_setopt(hnd, CURLOPT_USERAGENT, "curl/7.54.0"); // [6]
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, slist1);
curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 50L); // [7]
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); // [8]
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "POST"); // [9]
curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L);
// [4] URL。
// [5] ターミナル上でプログレスバー表示をオンにする。
// [6] BASIC認証に用いるユーザ名とパスワードを指定。
// [7] HTTPのリダイレクト先を追いかける数の最大値。
// [8] HTTPS通信をするための指定。
// [9] POSTメソッドの使用を明示する指定。
// [?] ポストフィールドの指定だけど、ここだけよく分からなかった。
ret = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
hnd = NULL;
curl_slist_free_all(slist1);
slist1 = NULL;
return (int)ret;
}
おわりに
ましなcurlラッパーライブラリ無いですか?
C++ REST SDKとcurlpp以外で、Windows環境でVS2017(VC++14)でビルドできるやつで。
追記:
公式リファレンスのようです。 https://ec.haxx.se/callback-progress.html
情報が新しいようなのでそのうちまとめたいです。