きっかけ
Linux環境でC言語のlibcurlの動作確認をしたかった。
調べたらWindows10 HomeでもDocker使えるようなので試してみる。
やったこと
- Dockerインストール
- Docker Hubへの登録
- ContOSコンテナを作成
- コンテナ内でC言語コンパイル&実行できるようにする
- コンテナにlibcurlをインストールする
- POSTの動作確認
Dockerの準備
Dockerのインストール
https://docs.docker.com/desktop/windows/install/
からDocker Desktop Installer.exeをDLしてインストール。
WSL2のインストール
ポップアップが出てWSL2をアップデートしてと言われるので
画面に従ってURLからインストールしてPC再起動。
https://aka.ms/wsl2kernel
→wsl_update_x64.msi
WSL2とは?
Windows Subsystem for Linux 2。
Windows 10上でLinuxを動作させるための仕組みのことらしい。それのバージョン2。
WSL2インストール後、Dockerのチュートリアルが始まるのでやってみる。
Docker Hubのアカウント作成
DockerイメージをプルしたりするにはDocker Hubへの登録が必要なのでしておく。
Docker Hub
https://hub.docker.com/
Dockerイメージの取得~コンテナ起動
今回はCentOS7の公式イメージを使うことにする。
Docker Hubで「Official Images」にチェックして「centos」で検索すると
公式のリポジトリが見つかる。
リポジトリを見ると以下の形式でCentOS7が取れそうなことがわかる。
$ docker pull centos:7
ターミナルから試して無事取得できた。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
~省略~
centos 7 eeb6ee3f44bd 4 months ago 204MB
取得したイメージからコンテナ作成&起動
$ docker run -it -d --name centos7c centos:7
-it:ログインして入出力可能にする
-d:コンテナから抜けてもバックグラウンドで起動したままにする
--name:コンテナに名前をつける
起動できた。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
df8c294e015f centos:7 "/bin/bash" 13 seconds ago Up 12 seconds centos7c
入ってみる。
$ docker exec -it centos7c /bin/bash
[root@df8c294e015f /]# ←コンテナに入った
似たようなコマンドにdocker attach ~
があるが、
これはexitで終了時にコンテナも終了してしまうよう。
その他のDockerコマンド
$ docker stop centos7c
$ docker ps -a
$ docker start centos7c
使用しなくなったコンテナはdocker rm centos7c
で削除できる。
削除するとdocker ps -a
でも表示されなくなる。
削除したらもちろん今までコンテナ内で行ったことはすべてなくなるので注意。
コンテナ内にC言語環境
gccのインストール
gccが入っているか確認
[root@df8c294e015f /]# rpm -qa gcc
[root@df8c294e015f /]#
入ってなかったのでインストール
[root@df8c294e015f /]# yum -y install gcc
~省略~
Dependency Updated:
glibc.x86_64 0:2.17-325.el7_9 glibc-common.x86_64 0:2.17-325.el7_9
Complete!
[root@df8c294e015f /]#
インストールされた。
[root@df8c294e015f /]# rpm -qa gcc
gcc-4.8.5-44.el7.x86_64
作業用ユーザ作成
[root@df8c294e015f /]# adduser user1
[root@df8c294e015f /]# su - user1
[user1@df8c294e015f ~]$ pwd
/home/user1
GCCでコンパイル
コンパイルできるか試してみる。
[user1@df8c294e015f ~]$ mkdir test01
[user1@df8c294e015f ~]$ cd test01/
[user1@df8c294e015f test01]$ cat test.c
# include<stdio.h>
int main(){
printf("hello\n");
}
[user1@df8c294e015f test01]$ gcc test.c
[user1@df8c294e015f test01]$ ./a.out
hello
無事コンパイルと実行ができた。
curl公式
https://curl.se/
libcurl込みでコンパイルしてみる
[user1@df8c294e015f test01]$ cat post.c
# include<stdio.h>
# include <curl/curl.h>
~省略~
[user1@df8c294e015f test01]$ gcc test.c -lcurl
fatal error: curl/curl.h: No such file or directory
-lオプションでリンクしたいライブラリ名を指定している。
libcurlを入れてみる
いろいろ調べてみるとC言語から#include <curl/curl.h>
するには
libcurl-devel
のインストールが必要のよう。
https://stackoverflow.com/questions/69633205/libcurl-c-how-to-correctly-install-and-use-on-centos-7
ということでlibcurl-develを入れる
# yum install libcurl-devel
~省略~
Downloading packages:
libcurl-devel-7.29.0-59.el7_9.1.x86_64.rpm | 303 kB 00:00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Installing : libcurl-devel-7.29.0-59.el7_9.1.x86_64 1/1
Verifying : libcurl-devel-7.29.0-59.el7_9.1.x86_64 1/1
Installed:
libcurl-devel.x86_64 0:7.29.0-59.el7_9.1
Complete!
インストール完了したら再度コンパイル。
[root@df8c294e015f test01]# gcc test.c -lcurl
[root@df8c294e015f test01]# ./a.out
コンパイルが通った!
モックサーバの用意
POSTのテストをしたいので何かよさそうなものはないか探してみると以下2つが簡単そうなのでそれぞれ試してみる。
- json-server
- httpbin.org
json-server
npmでインストール。npm自体のインストールについては割愛。
$ npm install -g json-server
リクエストした際に返すレスポンスをjsonで定義しておく。
{
"user": {
"name": "user1",
"age": 30
}
}
作成したjsonを指定して起動。
$ json-server --watch db.json
ブラウザから以下にアクセスすると
http://localhost:3000/user
db.jsonで定義したuserオブジェクトの中身を返してくれる。
すごく簡単。
ただPOSTを試してみると、用意したdb.jsonを本当にPOSTしたデータで書き換えてしまう。
POSTで書き換えないようにするにはもうひと手間いるよう。(Javascriptでの実装が少し必要)
httpbin.org
こちらは簡単なHTTPリクエストを試せるWebサービス。
http://httpbin.org
Dockerイメージも用意されているのでPullしてローカルで動かせば外部に通信する必要もない。
いったんDockerイメージではなくWebサービスをそのまま使ってみる。
$ curl -X GET "http://httpbin.org/get?name=user1&age=30"
{
"args": {
"age": "30",
"name": "user1"
},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.79.1",
"X-Amzn-Trace-Id": "Root=1-61ed8bac-11b4622d4e26172d4ab0b672"
},
"origin": "133.200.49.161",
"url": "http://httpbin.org/get?name=user1&age=30"
}
GETの場合は"args"にリクエストパラメータを設定して返してくれる。
$ curl -X POST "http://httpbin.org/post" -d "name=user1&age=30"
{
"args": {},
"data": "",
"files": {},
"form": {
"age": "30",
"name": "user1"
},
"headers": {
"Accept": "*/*",
"Content-Length": "17",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.79.1",
"X-Amzn-Trace-Id": "Root=1-61ed8be5-4814397c319394335e8739a0"
},
"json": null,
"origin": "133.200.49.161",
"url": "http://httpbin.org/post"
}
POSTの場合は"form"にリクエストパラメータを設定して返してくれる。(おそらくformタグでSubmitしたイメージ)
-H "Content-Type: application/json"
を指定すれば"data"にPOSTデータをそのまま設定して返してくれる。
curl -X POST "http://httpbin.org/post" -H "Content-Type: application/json" -d '{"name":"user1","age":30}'
{
"args": {},
"data": "'{name:user1,age:30}'",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "21",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "curl/7.79.1",
"X-Amzn-Trace-Id": "Root=1-61ed8dbd-672e05e12b0709182676ec4d"
},
"json": null,
"origin": "133.200.49.161",
"url": "http://httpbin.org/post"
}
↑ちなみに"data"はWindowsコマンドプロンプトだと
"data": "'{name:user1,age:30}'",
で
DockerコンテナのLinuxだと
"data": "{\"name\":\"user1\",\"age\":30}",
だった。
POST処理の実装
公式ドキュメントを参考に実装。
https://curl.se/libcurl/c/getinmemory.html
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <curl/curl.h>
# define MAX_BUF 65535
typedef struct
{
char *memory;
int size;
} Chunk;
// Callback function
static size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
Chunk *chunk = (Chunk *)userp;
char *ptr = realloc(chunk->memory, chunk->size + realsize + 1);
if (!ptr)
{
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
chunk->memory = ptr;
memcpy(&(chunk->memory[chunk->size]), buffer, realsize);
chunk->size += realsize;
chunk->memory[chunk->size] = 0;
return realsize;
}
int main()
{
printf("### POST START ###\n");
CURL *curl;
CURLcode ret;
int wr_error = 0;
curl = curl_easy_init();
char *post_data = "name=user2&age=31";
Chunk chunk;
memset(&chunk, 0, sizeof(Chunk));
if (curl == NULL)
{
fprintf(stderr, "curl_easy_init failed.\n");
return 1;
}
curl_easy_setopt(curl, CURLOPT_URL, "http://httpbin.org/post");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
// post option
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(post_data));
ret = curl_easy_perform(curl);
printf("ret : %d\n", ret);
if (ret == 0)
{
printf("size : %d\n", chunk.size);
printf("response :\n%s\n", chunk.memory);
}
curl_easy_cleanup(curl);
printf("### POST END ###\n");
return 0;
}
[user1@df8c294e015f test04]$ ./a.out
### POST START ###
ret : 0
size : 413
response :
{
"args": {},
"data": "",
"files": {},
"form": {
"age": "31",
"name": "user2"
},
~省略~
### POST END ###
その他
Dockerで実装したファイルをホストに渡す
docker cp centos7c:/home/user1/test01/test.c .
docker cp <コンテナ名>:<コンテナ内ファイル絶対パス> <コピー先ホストディレクトリ>
の形式
すでにtest.cがコピー先ホストディレクトリにある場合は上書きされるので注意。
ホストからコンテナにファイルを渡す
docker cp test.c centos7c:/home/user1/test02
docker cp <コピーするファイル> <コンテナ名>:<コンテナ内ファイル絶対パス>
の形式
あとがき
実は最初VMWareで試そうとしたけど、
CentOS7のISOイメージDLしてVMWareで仮想マシン作成して起動したら
以下のエラーが発生してよくわからなかったのでDocker使ってみました。
Linux install error "dracut Warning: Can't mount root filesystem."
あとでlibcurlインストールまでの作業をDockerfileにまとめてDockerイメージ化したい。