C言語でPOSTリクエストを送る

# はじめに

C言語(g++でビルドするけれど)でHTTPでデータを送る方法を調べていました。
POSTでセンサーデータを送りたいなー、と思っていた所、libcurl というライブラリをC/C++で使うことができることを知ったので、頑張って動かしてみました。あっさり動いたので、いちおうメモしておきます。

自分のコード：[こちら](https://github.com/torupati/http_send_data/releases/tag/v0.1)

参考にしたtutorial: [こちら](https://curl.se/libcurl/c/libcurl-tutorial.html)

# POST リクエストをC言語で送ってみる

## 準備
 
libcurl だけではcurl/curl.h がないと言われたので、apt update してから

```
$ sudo apt install libcurl4-openssl-dev
```

でインストールしました。

## POST リクエストを受け取るサーバ

いつもの

```
$ python -m http.server 8000
```

でサーバを立てようとしたのですが、POSTを受け取るにはCGIで値を受け取るようになど分からなかったので、やめました。
https://docs.python.org/ja/3/library/http.server.html


今回は趣向を変えて、golang で用意しました。goの環境はapt で入りました。

```
$ sudo apt install golang-go
$ go version
go version go1.13.8 linux/amd64
```

一式は /usr/share/go 以下にインストールされた、っぽいです。GOROOTとかGOPATHとか環境変数の設定していないけれどいいのか、、、分かりませんが、先に進みます。

[このコード](https://curl.se/libcurl/c/libcurl-tutorial.html)をそのまま使いました。下記に、GOのコードの作者の解説があります。多謝。

https://qiita.com/ayatothos/items/addb688181d96a9eecf4

ビルドして実行します。

```
$ go build main.go
$ ./main
```

別ターミナルでPOSTしてみます。

```
$ curl -X POST -d "foo=1000&hoge=aiueo" http://127.0.0.1:8080/sample1
```

サーバを立ち上げている方のターミナルで、受け取ったことが表示されます。

```
$ ./main
hoge aiueo
foo 1000
```

めでたしめでたし。尚、curl の使い方は下記を参考にしました。

https://weblabo.oscasierra.net/curl-post/

## libcurl を使用したC言語でのHTTP POSTするclientを実装

### パラメータを送る

ずばり下記を参考に書いてみました。

https://curl.se/libcurl/c/http-post.html

手順としては、

- curl_global_init(...) を最初に呼ぶ。socket 以下のHWよりのソフトの準備かな？
- [curl_easy_init(...)](https://everything.curl.dev/libcurl/easyhandle) でハンドラを作る。socket のイメージだけと違うかな。
- [curl_easy_setopt(...)](https://everything.curl.dev/libcurl/options) でTCP client としての接続先サーバと送るメッセージの準備。SSLとかも設定できる。なdocumentを参照。何度もこの関数を呼んで設定する。ドキュメントを読もう。
- curl_easy_perform(...) でいざ通信を実行。これはblocking モード。non-blocking では別の関数を使う。
- curl_easy_cleanup(...) で作ったハンドラをクリアする。
- curl_global_cleanup()を最後に呼ぶ。

で動いた。実際はsetopt と perform を繰り返し使うのかな。エラーが起きて再接続するときは curl_easy_init でハンドラを作りなして。それぞれドキュメントがあり、使い方コードもあってとても参考になりました。

下記のコードであっさりと動きました。

```cpp:main.cc
#include <curl/curl.h>

int main(void)
{
    curl_global_init(CURL_GLOBAL_ALL);
    // https://curl.se/libcurl/c/curl_global_init.html
    printf("libcurl version %s\n", curl_version());

    CURL *easy_handle = curl_easy_init();
    /* set URL to operate on */
    if(easy_handle){
        CURLcode res = curl_easy_setopt(easy_handle, CURLOPT_URL, "http://127.0.0.1:8080/sample1");
        if(res == CURLE_OK){
            printf("OK set url\n");
        }else{
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }
        res = curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, "foo=1000&hoge=aiueo");
        if(res == CURLE_OK){
            printf("OK setopt CURLOPT_POSTFIELDS\n");
        }else{
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }
        res = curl_easy_perform(easy_handle);
        if(res == CURLE_OK){
            printf("OK easy_perform\n");
        }else{
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }
        curl_easy_cleanup(easy_handle);
    }
    curl_global_cleanup();
    return 0;
}
```

ビルドは下記で。pkg-config を使うと少しかっこいいのかな。

```
$　g++ main.cc `pkg-config --libs libcurl`
$ ./a.out
libcurl version libcurl/7.68.0 OpenSSL/1.1.1f zlib/1.2.11 brotli/1.0.7 libidn2/2.2.0 libpsl/0.21.0 (+libidn2/2.2.0) libssh/0.9.3/openssl/zlib nghttp2/1.40.0 librtmp/2.3
OK set url
OK setopt CURLOPT_POSTFIELDS
OK easy_perform
```

さきほどのサーバに同じような反応が見られます。

サーバを立てていないとエラーの内容をきちんと返してくれました。

```
OK set url
OK setopt CURLOPT_POSTFIELDS
curl_easy_perform() failed: Couldn't connect to server
```

### バイナリデータを送る

POSTリクエストでは、先程パラメータをJSONのような辞書形式のパラメータで送る以外に、直接バイナリデータを送ることもできます。その場合、libcurl では、curl_easy_setopt でデータの長さとデータの配列のポインタを渡します。

ポイントとしてはデータの長さはuint16_t で定義した変数だとダメで、きちんと curl_off_t を使いましょう。[こちら](https://manpages.org/CURLOPT_POSTFIELDS/3)を参考にしました。

```c
#include <curl/curl.h>

int main(void)
{
    curl_global_init(CURL_GLOBAL_ALL);
    printf("libcurl version %s\n", curl_version());

    CURL *easy_handle = curl_easy_init();
    /* set URL to operate on */
    if(easy_handle){
        CURLcode res = curl_easy_setopt(easy_handle, CURLOPT_URL, "http://127.0.0.1:8080/bytearray");
        if(res != CURLE_OK){
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }
        // Here, length and pointer of byte array is passed to easy_handel
        uint32_t data_num = 1024*1024;
        curl_off_t length_of_data = data_num*2;
        uint16_t* data = (uint16_t*)malloc(data_num*sizeof(uint16_t));
        res = curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDSIZE_LARGE, length_of_data);
        if(res != CURLE_OK){
            fprintf(stderr, "curl_easy_setopt(CURLOPT_POSTFIELDSIZE) failed: %s\n", curl_easy_strerror(res));
        }
        res = curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, reinterpret_cast<uint8_t*>(data));
        if(res != CURLE_OK){
            fprintf(stderr, "curl_easy_setopt(CURLOPT_POSTFIELDS) failed: %s\n", curl_easy_strerror(res));
        }
        res = curl_easy_perform(easy_handle);
        if(res != CURLE_OK){
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }
        free(data); data = NULL;
       curl_easy_cleanup(easy_handle);
    }
    curl_global_cleanup();
    return 0;
}
```

受け取るサーバ側も変更します。http.HandleFunc("/bytearray", myhandler) で新しい設定するハンドラーで、リクエストのbody を読むことで、送られてきたバイナリデータを取得できます。

```go
func myhandler(w http.ResponseWriter, request *http.Request) {
	fmt.Println("bindata_handler")
	d, err := ioutil.ReadAll(request.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}
	tmpfile, err := os.Create("./" + "data.bin")
	defer tmpfile.Close()
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}
	tmpfile.Write(d)

	w.WriteHeader(200)
	return
}
```

無事に動きました。

# まとめ

とりあえず、HTTP POST リクエストを受け取る簡易サーバをローカルに立てて、libcurl を使用したCコードで POST リクエストを遅れることが確認できた。これ、RasPiとかで使えるかな。。
今年はとりあえず幸先よくスタートできたぞ。
(2024/1/2)