Libcurl使用方法

前言

以前都是直接使用curl的binary執行檔,不過工作的開發環境是一個嵌入式系統,可能有安全性的考量所以並沒有porting curl,不過可以透過curl的library API在build time的時候把功能做進去,這邊紀錄一下過程。

環境與安裝

安裝libcurl

我的操作環境是Ubuntu Bionic (18.04LTS),可以直接用apt-get取得:

1
sudo apt-get install libcurl4-openssl-dev

安裝完之後可以確認一下header檔與library的位置:

架設測試用HTTP Server

為了測試功能,這邊用Python簡單架設一個HTTP servers,支援GET與POST兩個method,並回傳一個JSON格式的response。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class ResquestHandler(BaseHTTPRequestHandler):
def do_GET(self):
data = {'result':'GET_OK'}
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())

def do_POST(self):
req_datas = self.rfile.read(int(self.headers['content-length']))
print(req_datas.decode())
data = {'result':'POST_OK'}
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode('utf-8'))

if __name__ == '__main__':
host = ('', 8000)
server = HTTPServer(host, ResquestHandler)
server.serve_forever()

測試GET:

測試POST:

Server端收到的內容:

使用方式

初始化

使用前要先透過curl_global_init初始化執行環境,以及curl_easy_init初始化session。
官方文件有說,curl_global_init至少要執行一次,如果你沒有執行,呼叫curl_easy_init時他也會自動幫你做一次,但是有可能在multi-threaded的狀況下會引發其他問題,建議還是自己手動呼叫一次。
此外,curl_easy_init一定要搭配curl_easy_cleanup做清除的動作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CURL *curl;
CURLcode res;
if (CURLE_OK != (res = curl_global_init(CURL_GLOBAL_ALL)))
{
printf("CURL Global Init fail\r\n");
return;
}

curl = curl_easy_init();
if (curl == NULL)
{
printf("CURL easy Init fail\r\n");
return ;
}

// Do some thing

curl_easy_cleanup(curl);

發送GET Request

透過curl_easy_setopt來設定要發送的requset相關options,例如URL、Port以及Header等…,接著透過curl_easy_perform發送request。

1
2
3
4
5
6
7
8
9
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost"); // 設定URL
curl_easy_setopt(curl, CURLOPT_PORT, 8000); // 設定Port
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 設定SSL certificate verify,0的話則是disable
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // 設定SSL certificate host name的verify,0的話則是disable
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 0L); // 設定SSL certificate status的verify,0的話則是disable
struct curl_slist *hs=NULL;
hs = curl_slist_append(hs, "Content-Type: application/json"); // 設定header:"Content-Type: application/json"
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs); // 將設定的header列表加入
res = curl_easy_perform(curl); // 送出request

發送POST Request

與發送GET Request類似,不過多了CURLOPT_POSTFIELDS這個option去設定POST的內容。

1
2
3
4
5
6
7
8
9
10
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost"); 
curl_easy_setopt(curl, CURLOPT_PORT, 8000);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 0L);
struct curl_slist *hs=NULL;
hs = curl_slist_append(hs, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"sendData\":\"dataContent\"}"); // POST內容
res = curl_easy_perform(curl);

接收Response

接收Response要設定CURLOPT_WRITEFUNCTION與CURLOPT_WRITEDATA兩個option。
CURLOPT_WRITEFUNCTION用來設定一個callback function,用來處理收到的資料內容。
CURLOPT_WRITEDATA用來指定傳給callback function的資料位置,預設是stdout。

由於回傳的資料大小不確定,官方的範例中使用一個MemoryStruct結構去保存資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct MemoryStruct {
char *memory;
size_t size;
};

size_t write_callback(char *contents, size_t size, size_t nmemb, void *userp){
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;

char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if(!ptr) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}

mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;

return realsize;
}

struct MemoryStruct chunk;
chunk.memory = malloc(1);
chunk.size = 0;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

struct MemoryStruct {
char *memory;
size_t size;
};

size_t write_callback(char *contents, size_t size, size_t nmemb, void *userp){
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;

char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if(!ptr) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}

mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;

return realsize;
}

int main(){
CURL *curl;
CURLcode res;

// 初始化
if (CURLE_OK != (res = curl_global_init(CURL_GLOBAL_ALL)))
{
printf("CURL Global Init fail\r\n");
return 1;
}
curl = curl_easy_init();
if (curl == NULL)
{
printf("CURL easy Init fail\r\n");
return 1;
}

// 發送GET Request
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost");
curl_easy_setopt(curl, CURLOPT_PORT, 8000);
// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 0L);
struct curl_slist *hs=NULL;
hs = curl_slist_append(hs, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
struct MemoryStruct chunk;
chunk.memory = malloc(1);
chunk.size = 0;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);

res = curl_easy_perform(curl);
if(res != CURLE_OK)
{
printf("CURL curl_easy_perform fail: %s\r\n", curl_easy_strerror(res));
return 1;
}
else{
printf("Response data:\r\n");
printf("%s\n",chunk.memory);
}

// 重置內容
curl_easy_reset(curl);

// 發送POST Request,內容為:{"sendData": "dataContent"}
curl_easy_setopt(curl, CURLOPT_URL, "http://localhost");
curl_easy_setopt(curl, CURLOPT_PORT, 8000);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"sendData\":\"dataContent\"}");
chunk.memory = malloc(1);
chunk.size = 0;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
{
printf("CURL curl_easy_perform fail: %s\r\n", curl_easy_strerror(res));
return 1;
}
else{
printf("Response data:\r\n");
printf("%s\n",chunk.memory);
}

curl_easy_cleanup(curl);
return 0;
}

編譯與執行

透過gcc進行編譯,指定curl library。

1
gcc curl.c -lcurl -o curlTest

執行結果:

Server端的結果:

若有需要可以再搭配JSON-C將response做進一步的處理。

參考連結

官方文件
HTTP Server


Libcurl使用方法
https://chris-suo.github.io/ChrisComplete/2022/06/22/Libcurl-Usage/
Author
Chris Suo
Posted on
June 22, 2022
Licensed under