1. edo_m18

    No comment

    edo_m18
Changes in body
Source | HTML | Preview
@@ -1,286 +1,330 @@
[前回](http://qiita.com/edo_m18/items/41770cba5c166f276a83)、[前々回](http://qiita.com/edo_m18/items/cef278d0c14d1371db3b)とHTTP(S)クライアントについて書きました。
が、当然すでにこうしたクライアントはライブラリが存在し、その中でも`curl`はコマンドラインでも使う有名なライブラリです。
今回はこれを使ってみようと思います。
## curlのインストール
`curl`コマンドは入っていると思いますが、ライブラリはどうやらMacでは入っていないようなのでインストールを行います。
```console
$ brew install curl
# // ... installing message ...
-L/usr/local/opt/curl/lib
-I/usr/local/opt/curl/include
```
という感じで、インストール先が表示されるのでこれをコンパイル時に指定します。
```console
$ gcc test.c -I/usr/local/opt/curl/include -lcurl
```
## 実行してみる
まずは簡単に実行してみます。
```c:test.c
int main(void) {
CURL *curl;
// curlのセットアップ
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "https://qiita.com/");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
// 実行
curl_easy_perform(curl);
// 後始末
curl_easy_cleanup(curl);
return EXIT_SUCCESS;
}
```
これを実行すると、qiita.comの情報が標準出力に表示されます。
curlはデフォルトの出力先は標準出力になっています。
しかし、ただ表示されるだけではまったく使い物になりません。
例えばAPIにアクセスした場合はJSONなりでデータが返ってくると思いますが、これを保持できなければなりませんね。
## callback関数を定義する
curlではcallback関数を指定することで、サーバから届いたデータの塊を伴ってその関数を呼び出してくれます。
### callback関数の定義
以下のような形で関数を作り、curlのセット用関数で指定します。
```c
size_t callback(char *ptr, size_t size, size_t nmemb, void *stream) {
int block = size * nmemb;
// do somoting.
}
```
`char *ptr`が実際に取得されたデータです。
`size`と`nmemb`はちょっとよく分かりませんが、おそらくデータのサイズを表しているんだと思います。(色々調べたけど具体的に言及してるのが見つからなかったorz)
そして最後のポインタが重要です。
ここに渡ってくるポインタは、上記の関数と同様に、セット用関数で設定したポインタが渡ってきます。
設定には自分で定義した構造体などのポインタを指定します。
それぞれ具体的には以下のように設定します。
```c
CURL *curl;
curl = curl_easy_init();
// callback関数の最後の引数に渡ってくるポインタを指定
curl_easy_setopt(curl, CURLOPT_WRITEDATA, anyPointer);
// callback関数を設定
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, funcPointer);
```
### CURLOPT_WRITEDATAを定義する
`CURLOPT_WRITEDATA`で指定したポインタが、上記で書いた「最後のポインタ」のに渡ってくる部分です。
ここのポインタは、関数の宣言を見てもらうとわかりますが`void *`型になっています。
つまりなんでもいいわけですね。
文章だけだと分かりづらいので、curlが呼ばれた回数をカウントアップするだけの簡単なサンプルコードを示します。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
struct counter {
int count;
};
size_t countup(char *ptr, size_t size, size_t nmemb, void *stream) {
struct counter *counter = (struct counter *)stream;
int block = size * nmemb;
if (!counter) {
return block;
}
counter->count++;
return block;
}
int main(void) {
CURL *curl;
struct counter *counter = (struct counter *)malloc(sizeof(struct counter));
counter->count = 0;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "https://qiita.com/");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, counter);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, countup);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
printf("Total count: %d\n", counter->count);
free(counter);
return EXIT_SUCCESS;
}
```
## データを保持する
上記の例でなんとなく使い方が分かるかと思います。
しかしカウントアップだけできてもしょうがないので、最後は実際にデータを保持して出力するサンプルを作ります。
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
struct Buffer {
char *data;
int data_size;
};
size_t buffer_writer(char *ptr, size_t size, size_t nmemb, void *stream) {
struct Buffer *buf = (struct Buffer *)stream;
int block = size * nmemb;
if (!buf) {
return block;
}
if (!buf->data) {
buf->data = (char *)malloc(block);
}
else {
buf->data = (char *)realloc(buf->data, buf->data_size + block);
}
if (buf->data) {
memcpy(buf->data + buf->data_size, ptr, block);
buf->data_size += block;
}
return block;
}
int main(void) {
CURL *curl;
struct Buffer *buf;
buf = (struct Buffer *)malloc(sizeof(struct Buffer));
buf->data = NULL;
buf->data_size = 0;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, "https://qiita.com/");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, buffer_writer);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
printf("%s\n", buf->data);
free(buf->data);
free(buf);
return EXIT_SUCCESS;
}
```
見てもらうと分かる通り、カウントアップのサンプルとほぼ同じです。
異なる点はバッファの保存部分です。
具体的には以下の部分です。
```c
// バッファ用構造体
struct Buffer {
char *data;
int data_size;
};
size_t buffer_writer(char *ptr, size_t size, size_t nmemb, void *stream) {
struct Buffer *buf = (struct Buffer *)stream;
int block = size * nmemb;
if (!buf) {
return block;
}
if (!buf->data) {
buf->data = (char *)malloc(block);
}
else {
buf->data = (char *)realloc(buf->data, buf->data_size + block);
}
if (buf->data) {
memcpy(buf->data + buf->data_size, ptr, block);
buf->data_size += block;
}
return block;
}
```
バッファを取り扱うための構造体を宣言しています。
このバッファに、受信したデータを貯めていきます。
その処理がこの部分。
```c
if (!buf->data) {
buf->data = (char *)malloc(block);
}
else {
buf->data = (char *)realloc(buf->data, buf->data_size + block);
}
if (buf->data) {
memcpy(buf->data + buf->data_size, ptr, block);
buf->data_size += block;
}
```
最初の受信時、つまり`buf->data`が`NULL`の場合は受信したデータ分のメモリを確保します。
すでにデータがある場合は既存のデータから、受信したデータ分のメモリを足した領域を再確保します。(`realloc`)
メモリの確保ができたらデータをメモリに書き込みます。
```c
if (buf->data) {
memcpy(buf->data + buf->data_size, ptr, block);
buf->data_size += block;
}
```
やっていることは、受信したデータを新しく確保した場所にコピーするだけです。
`buf->data + buf->data_size`の部分はポインタのアドレスを、現在受信済のデータ分だけ進めています。その場所から、今回受信したデータを追加で書き込みます。
最後に、受信したデータサイズを更新して終わりです。
受信が終わったら、バッファに保存したデータを表示しています。
コネクションに成功すれば`qiita.com`のHTMLがだーっと表示されます。
HTMLを表示してもあまり面白みはありませんが、例えばAPIなどにアクセスしてJSONを取得し、それを使用する、という場合も同じように処理することで可能になります。
これでAPIからデータを取ってきてなにかする、ということができるようになりますね。
+
+
+[追記]
+
+## リクエストにヘッダとPOSTデータを含める
+
+上記でリクエストすることができました。
+が、APIからレスポンスを得るためにはヘッダを操作したり、POSTデータを付与する必要があります。
+
+### ヘッダを追加する
+
+ヘッダの追加は以下のようにします。
+
+```c
+struct curl_slist *headers = NULL;
+
+headers = curl_slist_append(headers, "Hoge: Fuga");
+
+// ... 中略
+
+curl_easy_setopt(curl, CURLOPT_HEADER, headers);
+
+// ... 中略
+
+curl_easy_perform(curl);
+```
+
+ヘッダの追加には`struct curl_slist`構造体を使います。
+
+
+### POSTデータを付与する
+
+POSTデータは以下のように付与します。
+
+```c
+// ... 中略
+char *post_data = "name=edo&age=20";
+
+curl_easy_setopt(curl, CURLOPT_POST, 1);
+curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
+```
+
+データの受信については同じです。
+これでAPIにリクエストを投げてレスポンスを受けることができます。