4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2024-01-02

はじめに

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

自分のコード:こちら

参考にしたtutorial: こちら

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とか環境変数の設定していないけれどいいのか、、、分かりませんが、先に進みます。

このコードをそのまま使いました。下記に、GOのコードの作者の解説があります。多謝。

ビルドして実行します。

$ 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 の使い方は下記を参考にしました。

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

パラメータを送る

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

手順としては、

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

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

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

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 を使いましょう。こちらを参考にしました。

#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 を読むことで、送られてきたバイナリデータを取得できます。

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)

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?