Skip to content

Commit 26c14a4

Browse files
committedFeb 20, 2025
[HttpClient重构与功能扩展]: 对Curl模块进行重构,扩展HTTP客户端功能并优化代码结构
- 重构HttpClient和HttpClientAsync类,引入PIMPL模式隐藏实现细节,提升代码可维护性 - 为HttpClient添加文件上传和下载功能,支持PUT和POST方法 - 为HttpClientAsync添加异步文件上传和下载功能,支持PUT和POST方法 - 添加文件工具类`file_utils`,提供文件创建、删除和数据断言功能 - 优化CMakeLists.txt,引入Google Test框架进行单元测试 - 修复HttpClient和HttpClientAsync中部分逻辑错误,提升代码稳定性 - 为TcpClient类添加错误信息获取功能,优化连接状态管理
1 parent f7337da commit 26c14a4

13 files changed

+1248
-404
lines changed
 

‎Curl/CMakeLists.txt

+29-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
add_executable(tcpclient_test tcpclient_test.cc tcpclient.cc tcpclient.hpp)
2-
target_link_libraries(tcpclient_test PRIVATE CURL::libcurl)
2+
target_link_libraries(
3+
tcpclient_test PRIVATE CURL::libcurl GTest::gtest GTest::gtest_main
4+
GTest::gmock GTest::gmock_main)
35

4-
add_executable(httpclient_test httpclient_test.cc httpclient.cc httpclient.hpp)
5-
target_link_libraries(httpclient_test PRIVATE CURL::libcurl)
6+
set(HttpClientSource file_utils.cc file_utils.hpp httpclient.cc httpclient.hpp)
67

7-
add_executable(httpclient_async_test httpclient_async_test.cc
8-
httpclient_async.cc httpclient_async.hpp)
9-
target_link_libraries(httpclient_async_test PRIVATE CURL::libcurl)
8+
add_executable(httpclient_test ${HttpClientSource} httpclient_test.cc)
9+
target_link_libraries(
10+
httpclient_test PRIVATE CURL::libcurl GTest::gtest GTest::gtest_main
11+
GTest::gmock GTest::gmock_main)
12+
13+
add_executable(httpclient_file_test ${HttpClientSource} httpclient_file_test.cc)
14+
target_link_libraries(
15+
httpclient_file_test PRIVATE CURL::libcurl GTest::gtest GTest::gtest_main
16+
GTest::gmock GTest::gmock_main)
17+
18+
set(HttpClientAsyncSource file_utils.cc file_utils.hpp httpclient_async.cc
19+
httpclient_async.hpp)
20+
21+
add_executable(httpclient_async_test ${HttpClientAsyncSource}
22+
httpclient_async_test.cc)
23+
target_link_libraries(
24+
httpclient_async_test PRIVATE CURL::libcurl GTest::gtest GTest::gtest_main
25+
GTest::gmock GTest::gmock_main)
26+
27+
add_executable(httpclient_async_file_test ${HttpClientAsyncSource}
28+
httpclient_async_file_test.cc)
29+
target_link_libraries(
30+
httpclient_async_file_test
31+
PRIVATE CURL::libcurl GTest::gtest GTest::gtest_main GTest::gmock
32+
GTest::gmock_main)

‎Curl/file_utils.cc

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#include "file_utils.hpp"
2+
3+
#include <gtest/gtest.h>
4+
5+
#include <filesystem>
6+
#include <fstream>
7+
#include <iostream>
8+
9+
void createFile(const std::string &filename, const std::string &data)
10+
{
11+
auto filepath = std::filesystem::current_path() / filename;
12+
std::filesystem::create_directories(filepath.parent_path());
13+
std::ofstream file(filepath);
14+
if (!file.is_open()) {
15+
std::cerr << "Cannot open the file: " << filepath << std::endl;
16+
return;
17+
}
18+
file << data;
19+
file.close();
20+
}
21+
22+
void removeFile(const std::string &filename)
23+
{
24+
auto filepath = std::filesystem::current_path() / filename;
25+
if (std::filesystem::exists(filepath)) {
26+
std::filesystem::remove(filepath);
27+
}
28+
}
29+
30+
void assertFileData(const std::string &filename, const std::string &data)
31+
{
32+
auto filepath = std::filesystem::current_path() / filename;
33+
std::ifstream file(filepath);
34+
if (!file.is_open()) {
35+
std::cerr << "Cannot open the file: " << filepath << std::endl;
36+
return;
37+
}
38+
std::string fileData((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));
39+
file.close();
40+
EXPECT_EQ(fileData, data);
41+
}
42+
43+
auto formatBytes(double value, int precision) -> std::string
44+
{
45+
std::vector<std::string> units = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
46+
47+
int i = 0;
48+
while (value > 1024) {
49+
value /= 1024;
50+
i++;
51+
}
52+
53+
std::ostringstream out;
54+
out.precision(precision);
55+
out << std::fixed << value;
56+
57+
return out.str() + " " + units[i];
58+
}

‎Curl/file_utils.hpp

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
void createFile(const std::string &filename, const std::string &data);
6+
7+
void removeFile(const std::string &filename);
8+
9+
void assertFileData(const std::string &filename, const std::string &data);
10+
11+
auto formatBytes(double value, int precision = 2) -> std::string;

‎Curl/httpclient.cc

+311-17
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,324 @@
11
#include "httpclient.hpp"
2+
#include "file_utils.hpp"
3+
4+
#include <cassert>
5+
#include <fstream>
6+
#include <iostream>
7+
#include <stdexcept>
8+
#include <utility>
9+
#include <vector>
10+
11+
class HttpClient::HttpClientPrivate
12+
{
13+
public:
14+
explicit HttpClientPrivate(HttpClient *q)
15+
: q_ptr(q)
16+
, curl(curl_easy_init())
17+
{
18+
if (curl == nullptr) {
19+
throw std::runtime_error("curl_easy_init() failed");
20+
}
21+
22+
// setVerbose();
23+
setHeaderCallback();
24+
}
25+
26+
void setVerbose()
27+
{
28+
assert(curl != nullptr);
29+
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
30+
}
31+
32+
void setHeaderCallback()
33+
{
34+
assert(curl != nullptr);
35+
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback);
36+
curl_easy_setopt(curl, CURLOPT_HEADERDATA, q_ptr);
37+
}
38+
39+
void setErrorCallback()
40+
{
41+
assert(curl != nullptr);
42+
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, q_ptr->d_ptr->errorBuffer.data());
43+
}
44+
45+
void setHeader(const Headers &headers)
46+
{
47+
assert(curl != nullptr);
48+
for (const auto &[key, value] : std::as_const(headers)) {
49+
this->headers = curl_slist_append(this->headers, (key + ": " + value).c_str());
50+
}
51+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, this->headers);
52+
}
53+
54+
void clear()
55+
{
56+
clearBuffer();
57+
curl_easy_cleanup(curl);
58+
}
59+
60+
void clearBuffer()
61+
{
62+
headerBuffer.clear();
63+
bodyBuffer.clear();
64+
errorBuffer.clear();
65+
curl_slist_free_all(headers);
66+
headers = nullptr;
67+
}
68+
69+
void printError()
70+
{
71+
if (!errorBuffer.empty()) {
72+
std::cerr << "Error: " << errorBuffer << std::endl;
73+
}
74+
}
75+
76+
static auto headerCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
77+
{
78+
auto *client = static_cast<HttpClient *>(userdata);
79+
client->d_ptr->headerBuffer.append(ptr, size * nmemb);
80+
return size * nmemb;
81+
}
82+
83+
static auto writeCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
84+
{
85+
auto *client = static_cast<HttpClient *>(userdata);
86+
client->d_ptr->bodyBuffer.append(ptr, size * nmemb);
87+
return size * nmemb;
88+
}
89+
90+
static auto downloadCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
91+
{
92+
auto *client = static_cast<HttpClient *>(userdata);
93+
assert(client->d_ptr->file.is_open());
94+
client->d_ptr->file.write(ptr, size * nmemb);
95+
return size * nmemb;
96+
}
97+
98+
static auto downloadProgressCallback(void *clientp,
99+
curl_off_t dltotal,
100+
curl_off_t dlnow,
101+
curl_off_t ultotal,
102+
curl_off_t ulnow) -> int
103+
{
104+
std::cout << "Download progress: " << formatBytes(dlnow) << "/" << formatBytes(dltotal)
105+
<< std::endl;
106+
return 0;
107+
}
108+
109+
static auto uploadCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
110+
{
111+
auto *client = static_cast<HttpClient *>(userdata);
112+
auto &file = client->d_ptr->file;
113+
assert(file.is_open());
114+
file.read(ptr, size * nmemb);
115+
return file.gcount();
116+
}
117+
118+
static auto uploadProgressCallback(void *clientp,
119+
curl_off_t dltotal,
120+
curl_off_t dlnow,
121+
curl_off_t ultotal,
122+
curl_off_t ulnow) -> int
123+
{
124+
std::cout << "Upload progress: " << formatBytes(ulnow) << "/" << formatBytes(ultotal)
125+
<< std::endl;
126+
return 0;
127+
}
128+
129+
HttpClient *q_ptr;
130+
131+
CURL *curl = nullptr;
132+
CURLcode res = CURLE_OK;
133+
struct curl_slist *headers = nullptr;
134+
std::string headerBuffer = {};
135+
std::string bodyBuffer = {};
136+
std::string errorBuffer = {};
137+
std::fstream file;
138+
};
139+
140+
HttpClient::HttpClient()
141+
: d_ptr(std::make_unique<HttpClientPrivate>(this))
142+
{}
143+
144+
HttpClient::~HttpClient()
145+
{
146+
d_ptr->clear();
147+
}
148+
149+
auto HttpClient::get(const std::string &url, const Headers &headers) -> std::string
150+
{
151+
return sendCustomRequest(url, "GET", "", headers);
152+
}
153+
154+
auto HttpClient::post(const std::string &url, const std::string &data, const Headers &headers)
155+
-> std::string
156+
{
157+
return sendCustomRequest(url, "POST", data, headers);
158+
}
159+
160+
auto HttpClient::put(const std::string &url, const std::string &data, const Headers &headers)
161+
-> std::string
162+
{
163+
return sendCustomRequest(url, "PUT", data, headers);
164+
}
165+
166+
auto HttpClient::del(const std::string &url, const Headers &headers) -> std::string
167+
{
168+
return sendCustomRequest(url, "DELETE", "", headers);
169+
}
170+
171+
auto HttpClient::options(const std::string &url, const Headers &headers) -> std::string
172+
{
173+
return sendCustomRequest(url, "OPTIONS", "", headers);
174+
}
175+
176+
auto HttpClient::patch(const std::string &url, const std::string &data, const Headers &headers)
177+
-> std::string
178+
{
179+
return sendCustomRequest(url, "PATCH", data, headers);
180+
}
2181

3182
auto HttpClient::sendCustomRequest(const std::string &url,
4183
const std::string &method,
5184
const std::string &data,
6185
const Headers &headers) -> std::string
7186
{
8-
m_buffer.clear();
9-
m_headers = nullptr;
187+
d_ptr->clearBuffer();
188+
189+
std::cout << "Sending (" << method << ") request to (" << url << ")" << std::endl;
190+
191+
curl_easy_setopt(d_ptr->curl, CURLOPT_URL, url.c_str());
192+
curl_easy_setopt(d_ptr->curl, CURLOPT_CUSTOMREQUEST, method.c_str());
193+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDS, data.c_str());
194+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDSIZE, data.size());
195+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEFUNCTION, HttpClientPrivate::writeCallback);
196+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEDATA, this);
197+
198+
d_ptr->setHeader(headers);
199+
200+
d_ptr->res = curl_easy_perform(d_ptr->curl);
201+
if (d_ptr->res != CURLE_OK) {
202+
throw std::runtime_error(curl_easy_strerror(d_ptr->res));
203+
}
204+
205+
d_ptr->printError();
206+
207+
return d_ptr->bodyBuffer;
208+
}
209+
210+
auto HttpClient::download(const std::string &url,
211+
const std::filesystem::path &path,
212+
const Headers &headers) -> bool
213+
{
214+
d_ptr->clearBuffer();
215+
216+
d_ptr->file.open(path, std::ios::out | std::ios::binary);
217+
if (!d_ptr->file.is_open()) {
218+
d_ptr->bodyBuffer = "Failed to open file: " + path.string();
219+
return false;
220+
}
221+
222+
std::cout << "Downloading (" << url << ") to (" << path << ")" << std::endl;
223+
224+
curl_easy_setopt(d_ptr->curl, CURLOPT_URL, url.c_str());
225+
curl_easy_setopt(d_ptr->curl, CURLOPT_CUSTOMREQUEST, "GET");
226+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEFUNCTION, HttpClientPrivate::downloadCallback);
227+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEDATA, this);
228+
curl_easy_setopt(d_ptr->curl, CURLOPT_NOPROGRESS, 0L);
229+
curl_easy_setopt(d_ptr->curl,
230+
CURLOPT_XFERINFOFUNCTION,
231+
HttpClientPrivate::downloadProgressCallback);
10232

11-
curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
12-
curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, method.c_str());
13-
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, data.c_str());
14-
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeCallback);
15-
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this);
16-
curl_easy_setopt(m_curl, CURLOPT_HEADERFUNCTION, headerCallback);
17-
curl_easy_setopt(m_curl, CURLOPT_HEADERDATA, this);
233+
d_ptr->setHeader(headers);
18234

19-
for (const auto &[key, value] : headers) {
20-
m_headers = curl_slist_append(m_headers, (key + ": " + value).c_str());
235+
d_ptr->res = curl_easy_perform(d_ptr->curl);
236+
237+
d_ptr->file.close();
238+
if (d_ptr->res != CURLE_OK) {
239+
throw std::runtime_error(curl_easy_strerror(d_ptr->res));
21240
}
22-
curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headers);
23241

24-
m_res = curl_easy_perform(m_curl);
25-
if (m_res != CURLE_OK) {
26-
throw std::runtime_error(curl_easy_strerror(m_res));
242+
d_ptr->printError();
243+
244+
return true;
245+
}
246+
247+
auto HttpClient::upload_put(const std::string &url,
248+
const std::filesystem::path &path,
249+
const Headers &headers) -> bool
250+
{
251+
d_ptr->clearBuffer();
252+
253+
d_ptr->file.open(path, std::ios::in | std::ios::binary);
254+
if (!d_ptr->file.is_open()) {
255+
std::cerr << "Failed to open file: " << path << std::endl;
256+
return false;
27257
}
28258

29-
return m_buffer;
30-
}
259+
std::cout << "Uploading (" << path << ") to (" << url << ")" << std::endl;
260+
261+
curl_easy_setopt(d_ptr->curl, CURLOPT_URL, url.c_str());
262+
curl_easy_setopt(d_ptr->curl, CURLOPT_CUSTOMREQUEST, "PUT");
263+
curl_easy_setopt(d_ptr->curl, CURLOPT_UPLOAD, 1L);
264+
curl_easy_setopt(d_ptr->curl, CURLOPT_READFUNCTION, HttpClientPrivate::uploadCallback);
265+
curl_easy_setopt(d_ptr->curl, CURLOPT_READDATA, this);
266+
curl_easy_setopt(d_ptr->curl, CURLOPT_NOPROGRESS, 0L);
267+
curl_easy_setopt(d_ptr->curl,
268+
CURLOPT_XFERINFOFUNCTION,
269+
HttpClientPrivate::uploadProgressCallback);
270+
271+
d_ptr->setHeader(headers);
272+
273+
d_ptr->res = curl_easy_perform(d_ptr->curl);
274+
275+
d_ptr->file.close();
276+
if (d_ptr->res != CURLE_OK) {
277+
throw std::runtime_error(curl_easy_strerror(d_ptr->res));
278+
}
279+
280+
d_ptr->printError();
281+
282+
return true;
283+
}
284+
285+
auto HttpClient::upload_post(const std::string &url,
286+
const std::filesystem::path &path,
287+
const Headers &headers) -> bool
288+
{
289+
d_ptr->clearBuffer();
290+
291+
std::cout << "Uploading (" << path << ") to (" << url << ")" << std::endl;
292+
293+
curl_easy_setopt(d_ptr->curl, CURLOPT_URL, url.c_str());
294+
curl_easy_setopt(d_ptr->curl, CURLOPT_CUSTOMREQUEST, "POST");
295+
curl_easy_setopt(d_ptr->curl, CURLOPT_NOPROGRESS, 0L);
296+
curl_easy_setopt(d_ptr->curl,
297+
CURLOPT_XFERINFOFUNCTION,
298+
HttpClientPrivate::uploadProgressCallback);
299+
300+
d_ptr->setHeader(headers);
301+
302+
auto *form = curl_mime_init(d_ptr->curl);
303+
auto *part = curl_mime_addpart(form);
304+
curl_mime_name(part, "file");
305+
curl_mime_filedata(part, path.string().c_str());
306+
curl_easy_setopt(d_ptr->curl, CURLOPT_MIMEPOST, form);
307+
308+
d_ptr->res = curl_easy_perform(d_ptr->curl);
309+
310+
if (d_ptr->res != CURLE_OK) {
311+
throw std::runtime_error(curl_easy_strerror(d_ptr->res));
312+
}
313+
314+
d_ptr->printError();
315+
316+
curl_mime_free(form);
317+
318+
return true;
319+
}
320+
321+
auto HttpClient::error() const -> std::string
322+
{
323+
return d_ptr->errorBuffer;
324+
}

‎Curl/httpclient.hpp

+27-61
Original file line numberDiff line numberDiff line change
@@ -4,80 +4,46 @@
44

55
#include <curl/curl.h>
66

7+
#include <filesystem>
78
#include <map>
8-
#include <stdexcept>
9-
#include <string>
9+
#include <memory>
1010

1111
class HttpClient : noncopyable
1212
{
1313
public:
1414
using Headers = std::map<std::string, std::string>;
1515

16-
HttpClient()
17-
: m_curl(curl_easy_init())
18-
, m_headers(nullptr)
19-
{
20-
if (m_curl == nullptr) {
21-
throw std::runtime_error("curl_easy_init() failed");
22-
}
23-
}
24-
~HttpClient()
25-
{
26-
curl_easy_cleanup(m_curl);
27-
curl_slist_free_all(m_headers);
28-
}
16+
HttpClient();
17+
~HttpClient();
2918

30-
auto get(const std::string &url, const Headers &headers = Headers()) -> std::string
31-
{
32-
return sendCustomRequest(url, "GET", "", headers);
33-
}
34-
auto post(const std::string &url, const std::string &data, const Headers &headers = Headers())
35-
-> std::string
36-
{
37-
return sendCustomRequest(url, "POST", data, headers);
38-
}
39-
auto put(const std::string &url, const std::string &data, const Headers &headers = Headers())
40-
-> std::string
41-
{
42-
return sendCustomRequest(url, "PUT", data, headers);
43-
}
44-
auto del(const std::string &url, const Headers &headers = Headers()) -> std::string
45-
{
46-
return sendCustomRequest(url, "DELETE", "", headers);
47-
}
48-
auto options(const std::string &url, const Headers &headers = Headers()) -> std::string
49-
{
50-
return sendCustomRequest(url, "OPTIONS", "", headers);
51-
}
52-
auto patch(const std::string &url, const std::string &data, const Headers &headers = Headers())
53-
-> std::string
54-
{
55-
return sendCustomRequest(url, "PATCH", data, headers);
56-
}
19+
auto get(const std::string &url, const Headers &headers = {}) -> std::string;
20+
auto post(const std::string &url, const std::string &data, const Headers &headers = {})
21+
-> std::string;
22+
auto put(const std::string &url, const std::string &data, const Headers &headers = {})
23+
-> std::string;
24+
auto del(const std::string &url, const Headers &headers = {}) -> std::string;
25+
auto options(const std::string &url, const Headers &headers = {}) -> std::string;
26+
auto patch(const std::string &url, const std::string &data, const Headers &headers = {})
27+
-> std::string;
5728

5829
auto sendCustomRequest(const std::string &url,
5930
const std::string &method,
6031
const std::string &data,
61-
const Headers &headers = Headers()) -> std::string;
32+
const Headers &headers = {}) -> std::string;
6233

63-
[[nodiscard]] auto error() const -> std::string { return m_buffer; }
34+
auto download(const std::string &url,
35+
const std::filesystem::path &path,
36+
const Headers &headers = {}) -> bool;
37+
auto upload_put(const std::string &url,
38+
const std::filesystem::path &path,
39+
const Headers &headers = {}) -> bool;
40+
auto upload_post(const std::string &url,
41+
const std::filesystem::path &path,
42+
const Headers &headers = {}) -> bool;
6443

65-
private:
66-
static auto writeCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
67-
{
68-
auto *client = static_cast<HttpClient *>(userdata);
69-
client->m_buffer.append(ptr, size * nmemb);
70-
return size * nmemb;
71-
}
72-
static auto headerCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
73-
{
74-
auto *client = static_cast<HttpClient *>(userdata);
75-
client->m_buffer.append(ptr, size * nmemb);
76-
return size * nmemb;
77-
}
44+
[[nodiscard]] auto error() const -> std::string;
7845

79-
CURL *m_curl;
80-
CURLcode m_res;
81-
struct curl_slist *m_headers;
82-
std::string m_buffer;
46+
private:
47+
class HttpClientPrivate;
48+
std::unique_ptr<HttpClientPrivate> d_ptr;
8349
};

‎Curl/httpclient_async.cc

+428-29
Large diffs are not rendered by default.

‎Curl/httpclient_async.hpp

+40-169
Original file line numberDiff line numberDiff line change
@@ -3,187 +3,58 @@
33
#include <utils/object.hpp>
44

55
#include <curl/curl.h>
6-
#include <curl/easy.h>
7-
#include <curl/multi.h>
86

9-
#include <atomic>
10-
#include <condition_variable>
7+
#include <filesystem>
118
#include <functional>
12-
#include <iostream>
139
#include <map>
14-
#include <mutex>
15-
#include <string>
16-
#include <thread>
1710

1811
class HttpClientAsync : noncopyable
1912
{
2013
public:
2114
using Callback = std::function<void(const std::string &)>;
2215
using Headers = std::map<std::string, std::string>;
2316

24-
HttpClientAsync() { init(); }
25-
~HttpClientAsync()
26-
{
27-
stop();
28-
cleanup();
29-
}
17+
HttpClientAsync();
18+
~HttpClientAsync();
3019

31-
void get(const std::string &url, const Headers &headers = Headers(), Callback callback = nullptr)
32-
{
33-
sendCustomRequest(url, "GET", "", headers, callback);
34-
}
35-
void post(const std::string &url,
36-
const std::string &data,
37-
const Headers &headers = Headers(),
38-
Callback callback = nullptr)
39-
{
40-
sendCustomRequest(url, "POST", data, headers, callback);
41-
}
42-
void put(const std::string &url,
43-
const std::string &data,
44-
const Headers &headers = Headers(),
45-
Callback callback = nullptr)
46-
{
47-
sendCustomRequest(url, "PUT", data, headers, callback);
48-
}
49-
void del(const std::string &url, const Headers &headers = Headers(), Callback callback = nullptr)
50-
{
51-
sendCustomRequest(url, "DELETE", "", headers, callback);
52-
}
53-
void options(const std::string &url,
54-
const Headers &headers = Headers(),
55-
Callback callback = nullptr)
56-
{
57-
sendCustomRequest(url, "OPTIONS", "", headers, callback);
58-
}
59-
void patch(const std::string &url,
20+
CURL *get(const std::string &url, const Headers &headers = {}, Callback callback = nullptr);
21+
CURL *post(const std::string &url,
6022
const std::string &data,
61-
const Headers &headers = Headers(),
62-
Callback callback = nullptr)
63-
{
64-
sendCustomRequest(url, "PATCH", data, headers, callback);
65-
}
66-
67-
void sendCustomRequest(const std::string &url,
68-
const std::string &method,
69-
const std::string &data,
70-
const Headers &headers,
71-
Callback callback);
23+
const Headers &headers = {},
24+
Callback callback = nullptr);
25+
CURL *put(const std::string &url,
26+
const std::string &data,
27+
const Headers &headers = {},
28+
Callback callback = nullptr);
29+
CURL *del(const std::string &url, const Headers &headers = {}, Callback callback = nullptr);
30+
CURL *options(const std::string &url, const Headers &headers = {}, Callback callback = nullptr);
31+
CURL *patch(const std::string &url,
32+
const std::string &data,
33+
const Headers &headers = {},
34+
Callback callback = nullptr);
35+
36+
CURL *sendCustomRequest(const std::string &url,
37+
const std::string &method,
38+
const std::string &data,
39+
const Headers &headers,
40+
Callback callback);
41+
42+
void cancel(CURL *handle);
43+
44+
CURL *download(const std::string &url,
45+
const std::filesystem::path &path,
46+
const Headers &headers = {},
47+
Callback callback = nullptr);
48+
CURL *upload_put(const std::string &url,
49+
const std::filesystem::path &path,
50+
const Headers &headers = {},
51+
Callback callback = nullptr);
52+
CURL *upload_post(const std::string &url,
53+
const std::filesystem::path &path,
54+
const Headers &headers = {},
55+
Callback callback = nullptr);
7256

7357
private:
74-
static auto writeCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
75-
{
76-
auto *context = static_cast<Context *>(userdata);
77-
context->response.append(ptr, size * nmemb);
78-
return size * nmemb;
79-
}
80-
static auto headerCallback(char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t
81-
{
82-
auto *context = static_cast<Context *>(userdata);
83-
context->response.append(ptr, size * nmemb);
84-
return size * nmemb;
85-
}
86-
static auto readCallback(char *buffer, size_t size, size_t nitems, void *instream) -> size_t
87-
{
88-
return 0;
89-
}
90-
static auto progressCallback(
91-
void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) -> int
92-
{
93-
return 0;
94-
}
95-
96-
struct Context
97-
{
98-
Callback callback;
99-
std::string response;
100-
};
101-
102-
void init()
103-
{
104-
m_multi = curl_multi_init();
105-
if (m_multi == nullptr) {
106-
throw std::runtime_error("curl_multi_init() failed");
107-
}
108-
109-
m_running = true;
110-
m_thread = std::thread(&HttpClientAsync::run, this);
111-
}
112-
void cleanup()
113-
{
114-
for (auto &it : m_contexts) {
115-
curl_multi_remove_handle(m_multi, it.first);
116-
curl_easy_cleanup(it.first);
117-
}
118-
m_contexts.clear();
119-
curl_multi_cleanup(m_multi);
120-
}
121-
122-
void addHandle(CURL *handle, Context *context)
123-
{
124-
std::lock_guard<std::mutex> lock(m_mutex);
125-
m_contexts[handle].reset(context);
126-
curl_multi_add_handle(m_multi, handle);
127-
}
128-
void removeHandle(CURL *handle)
129-
{
130-
std::lock_guard<std::mutex> lock(m_mutex);
131-
m_contexts.erase(handle);
132-
curl_multi_remove_handle(m_multi, handle);
133-
curl_easy_cleanup(handle);
134-
}
135-
void checkMultiInfo()
136-
{
137-
CURLMsg *message = nullptr;
138-
int pending = 0;
139-
while ((message = curl_multi_info_read(m_multi, &pending)) != nullptr) {
140-
switch (message->msg) {
141-
case CURLMSG_DONE: {
142-
CURL *handle = message->easy_handle;
143-
auto it = m_contexts.find(handle);
144-
if (it == m_contexts.end()) {
145-
throw std::runtime_error("curl_multi_info_read() failed");
146-
}
147-
auto *context = it->second.get();
148-
context->callback(context->response);
149-
removeHandle(handle);
150-
break;
151-
}
152-
default: throw std::runtime_error("curl_multi_info_read() failed");
153-
}
154-
}
155-
}
156-
157-
void run()
158-
{
159-
int stillRunning = 0;
160-
while (m_running) {
161-
curl_multi_perform(m_multi, &stillRunning);
162-
checkMultiInfo();
163-
164-
std::unique_lock<std::mutex> lock(m_mutex);
165-
m_condition.wait_for(lock, std::chrono::milliseconds(100), [this] {
166-
int stillRunning = 0;
167-
curl_multi_perform(m_multi, &stillRunning);
168-
return stillRunning == 0;
169-
});
170-
}
171-
}
172-
void stop()
173-
{
174-
m_running = false;
175-
m_condition.notify_one();
176-
177-
if (m_thread.joinable()) {
178-
m_thread.join();
179-
}
180-
}
181-
182-
CURLM *m_multi;
183-
std::thread m_thread;
184-
std::atomic_bool m_running;
185-
std::mutex m_mutex;
186-
std::condition_variable m_condition;
187-
std::map<CURL *, std::unique_ptr<Context>> m_contexts;
188-
std::string m_buffer;
58+
class HttpClientAsyncPrivate;
59+
std::unique_ptr<HttpClientAsyncPrivate> d_ptr;
18960
};

‎Curl/httpclient_async_file_test.cc

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#include "file_utils.hpp"
2+
#include "httpclient_async.hpp"
3+
4+
#include <gtest/gtest.h>
5+
6+
#include <thread>
7+
#include <utility>
8+
9+
static HttpClientAsync httpClientAsync;
10+
static std::condition_variable cv;
11+
static std::mutex mutex;
12+
13+
static std::vector<std::string> paths = {"", "curl", "curl/1", "curl/1/2", "curl/1/2/3"};
14+
15+
void wait()
16+
{
17+
std::unique_lock<std::mutex> lock(mutex);
18+
cv.wait(lock);
19+
}
20+
21+
void test_download(const std::string &filename)
22+
{
23+
auto filepath = std::filesystem::current_path() / filename;
24+
auto url = "http://127.0.0.1:8080/files/" + filename;
25+
26+
httpClientAsync.download(url, filepath, {}, [](const std::string &) { cv.notify_one(); });
27+
wait();
28+
// 等待Content销毁,fstream完全关闭
29+
std::this_thread::sleep_for(std::chrono::milliseconds(200));
30+
}
31+
32+
void test_upload_put(const std::string &filename)
33+
{
34+
auto filepath = std::filesystem::current_path() / filename;
35+
auto url = "http://127.0.0.1:8080/files/" + filename;
36+
httpClientAsync.upload_put(url, filepath, {}, [](const std::string &) { cv.notify_one(); });
37+
wait();
38+
}
39+
40+
void test_upload_post(const std::string &path, const std::string &filename)
41+
{
42+
auto filepath = std::filesystem::current_path() / filename;
43+
std::string url = "http://127.0.0.1:8080/files";
44+
if (!path.empty()) {
45+
url += "/" + path;
46+
}
47+
httpClientAsync.upload_post(url, filepath, {}, [](const std::string &) { cv.notify_one(); });
48+
wait();
49+
}
50+
51+
void test_delete(const std::string &filename)
52+
{
53+
auto url = "http://127.0.0.1:8080/files/" + filename;
54+
httpClientAsync.del(url, {}, [](const std::string &) { cv.notify_one(); });
55+
wait();
56+
}
57+
58+
TEST(UploadTest, UploadPutFile)
59+
{
60+
auto filename = "curl_async_upload_put_file.txt";
61+
auto data = "curl async upload put file data";
62+
for (const auto &path : std::as_const(paths)) {
63+
std::string filepath = filename;
64+
std::string filedata = data;
65+
if (!path.empty()) {
66+
filepath = path + "/" + filename;
67+
filedata += " " + path;
68+
}
69+
70+
test_delete(filepath);
71+
72+
createFile(filepath, filedata);
73+
test_upload_put(filepath);
74+
75+
removeFile(filepath);
76+
test_download(filepath);
77+
assertFileData(filepath, filedata);
78+
}
79+
}
80+
81+
TEST(UploadTest, UploadPostFile)
82+
{
83+
auto filename = "curl_async_upload_post_file.txt";
84+
auto data = "curl async upload post file data";
85+
for (const auto &path : std::as_const(paths)) {
86+
std::string filepath = filename;
87+
std::string filedata = data;
88+
if (!path.empty()) {
89+
filepath = path + "/" + filename;
90+
filedata += " " + path;
91+
}
92+
test_delete(filepath);
93+
94+
createFile(filepath, filedata);
95+
test_upload_post(path, filepath);
96+
97+
removeFile(filepath);
98+
99+
test_download(filepath);
100+
assertFileData(filepath, filedata);
101+
}
102+
}
103+
104+
auto main(int argc, char *argv[]) -> int
105+
{
106+
testing::InitGoogleTest(&argc, argv);
107+
return RUN_ALL_TESTS();
108+
}

‎Curl/httpclient_async_test.cc

+25-29
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,50 @@
11
#include "httpclient_async.hpp"
22

3+
#include <iostream>
4+
#include <thread>
5+
36
auto main() -> int
47
{
8+
auto url = "http://www.baidu.com?name=hello&age=18";
9+
510
HttpClientAsync client;
611

7-
client.get("http://www.baidu.com", {}, [](const std::string &response) {
8-
std::cout << "GET response: " << std::endl;
12+
// client.get("http://www.baidu.com", {}, [](const std::string &response) {
13+
// std::cout << "GET response: " << std::endl;
14+
// std::cout << response << std::endl;
15+
// });
16+
17+
client.post(url, "hello world", {}, [](const std::string &response) {
18+
std::cout << "POST response: " << std::endl;
19+
std::cout << response << std::endl;
20+
});
21+
22+
client.put(url, "hello world", {}, [](const std::string &response) {
23+
std::cout << "PUT response: " << std::endl;
924
std::cout << response << std::endl;
1025
});
1126

12-
client.post("http://www.baidu.com",
13-
"hello world",
14-
{{"Content-Type", "text/plain"}},
15-
[](const std::string &response) {
16-
std::cout << "POST response: " << std::endl;
17-
std::cout << response << std::endl;
18-
});
19-
20-
client.put("http://www.baidu.com",
21-
"hello world",
22-
{{"Content-Type", "text/plain"}},
23-
[](const std::string &response) {
24-
std::cout << "PUT response: " << std::endl;
25-
std::cout << response << std::endl;
26-
});
27-
28-
client.del("http://www.baidu.com", {}, [](const std::string &response) {
27+
client.del(url, {}, [](const std::string &response) {
2928
std::cout << "DELETE response: " << std::endl;
3029
std::cout << response << std::endl;
3130
});
3231

33-
client.options("http://www.baidu.com", {}, [](const std::string &response) {
32+
client.options(url, {}, [](const std::string &response) {
3433
std::cout << "OPTIONS response: " << std::endl;
3534
std::cout << response << std::endl;
3635
});
3736

38-
client.patch("http://www.baidu.com",
39-
"hello world",
40-
{{"Content-Type", "text/plain"}},
41-
[](const std::string &response) {
42-
std::cout << "PATCH response: " << std::endl;
43-
std::cout << response << std::endl;
44-
});
37+
client.patch(url, "hello world", {}, [](const std::string &response) {
38+
std::cout << "PATCH response: " << std::endl;
39+
std::cout << response << std::endl;
40+
});
4541

46-
client.sendCustomRequest("http://www.baidu.com", "GET", "", {}, [](const std::string &response) {
42+
client.sendCustomRequest(url, "GET", "", {}, [](const std::string &response) {
4743
std::cout << "Custom request response: " << std::endl;
4844
std::cout << response << std::endl;
4945
});
5046

51-
std::this_thread::sleep_for(std::chrono::seconds(3));
47+
std::this_thread::sleep_for(std::chrono::seconds(5));
5248

5349
return 0;
5450
}

‎Curl/httpclient_file_test.cc

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include "file_utils.hpp"
2+
#include "httpclient.hpp"
3+
4+
#include <gtest/gtest.h>
5+
6+
#include <utility>
7+
8+
std::vector<std::string> paths = {"", "curl", "curl/1", "curl/1/2", "curl/1/2/3"};
9+
10+
void test_download(const std::string &filename)
11+
{
12+
auto filepath = std::filesystem::current_path() / filename;
13+
auto url = "http://127.0.0.1:8080/files/" + filename;
14+
HttpClient httpClient;
15+
httpClient.download(url, filepath);
16+
}
17+
18+
void test_upload_put(const std::string &filename)
19+
{
20+
auto filepath = std::filesystem::current_path() / filename;
21+
auto url = "http://127.0.0.1:8080/files/" + filename;
22+
HttpClient httpClient;
23+
httpClient.upload_put(url, filepath);
24+
}
25+
26+
void test_upload_post(const std::string &path, const std::string &filename)
27+
{
28+
auto filepath = std::filesystem::current_path() / filename;
29+
std::string url = "http://127.0.0.1:8080/files";
30+
if (!path.empty()) {
31+
url += "/" + path;
32+
}
33+
HttpClient httpClient;
34+
httpClient.upload_post(url, filepath);
35+
}
36+
37+
void test_delete(const std::string &filename)
38+
{
39+
auto url = "http://127.0.0.1:8080/files/" + filename;
40+
HttpClient httpClient;
41+
httpClient.del(url);
42+
}
43+
44+
TEST(UploadTest, UploadPutFile)
45+
{
46+
auto filename = "curl_upload_put_file.txt";
47+
auto data = "curl upload put file data";
48+
for (const auto &path : std::as_const(paths)) {
49+
std::string filepath = filename;
50+
std::string filedata = data;
51+
if (!path.empty()) {
52+
filepath = path + "/" + filename;
53+
filedata += " " + path;
54+
}
55+
56+
test_delete(filepath);
57+
58+
createFile(filepath, filedata);
59+
test_upload_put(filepath);
60+
61+
removeFile(filepath);
62+
test_download(filepath);
63+
assertFileData(filepath, filedata);
64+
}
65+
}
66+
67+
TEST(UploadTest, UploadPostFile)
68+
{
69+
auto filename = "curl_upload_post_file.txt";
70+
auto data = "curl upload post file data";
71+
for (const auto &path : std::as_const(paths)) {
72+
std::string filepath = filename;
73+
std::string filedata = data;
74+
if (!path.empty()) {
75+
filepath = path + "/" + filename;
76+
filedata += " " + path;
77+
}
78+
test_delete(filepath);
79+
80+
createFile(filepath, filedata);
81+
test_upload_post(path, filepath);
82+
83+
removeFile(filepath);
84+
85+
test_download(filepath);
86+
assertFileData(filepath, filedata);
87+
}
88+
}
89+
90+
auto main(int argc, char *argv[]) -> int
91+
{
92+
testing::InitGoogleTest(&argc, argv);
93+
return RUN_ALL_TESTS();
94+
}

‎Curl/httpclient_test.cc

+8-14
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,30 @@
44

55
auto main() -> int
66
{
7+
auto url = "http://www.baidu.com?name=hello&age=18";
8+
79
HttpClient client;
810

911
// 为什么用了get方法,下面的其他方法都不输出了?从WireShark看,确实是发送了请求,也收到了响应,但是就是不输出
1012
// std::cout << "get_data: " << client.get("http://www.baidu.com") << std::endl;
1113
// std::cout << "--------------------------" << std::endl;
1214

13-
std::cout << "post_data: "
14-
<< client.post("http://www.baidu.com?name=hello&age=18", "hello world") << std::endl;
15+
std::cout << client.post(url, "hello world") << std::endl;
1516
std::cout << "--------------------------" << std::endl;
1617

17-
std::cout << "put_data: " << client.put("http://www.baidu.com?name=hello&age=18", "hello world")
18-
<< std::endl;
18+
std::cout << client.put(url, "hello world") << std::endl;
1919
std::cout << "--------------------------" << std::endl;
2020

21-
std::cout << "del_data: " << client.del("http://www.baidu.com?name=hello&age=18") << std::endl;
21+
std::cout << client.del(url) << std::endl;
2222
std::cout << "--------------------------" << std::endl;
2323

24-
std::cout << "options_data: " << client.options("http://www.baidu.com?name=hello&age=18")
25-
<< std::endl;
24+
std::cout << client.options(url) << std::endl;
2625
std::cout << "--------------------------" << std::endl;
2726

28-
std::cout << "patch_data: "
29-
<< client.patch("http://www.baidu.com?name=hello&age=18", "hello world") << std::endl;
27+
std::cout << client.patch(url, "hello world") << std::endl;
3028
std::cout << "--------------------------" << std::endl;
3129

32-
std::cout << "custom_data: "
33-
<< client.sendCustomRequest("http://www.baidu.com?name=hello&age=18",
34-
"GET",
35-
"hello world")
36-
<< std::endl;
30+
std::cout << client.sendCustomRequest(url, "GET", "hello world") << std::endl;
3731

3832
return 0;
3933
}

‎Curl/tcpclient.cc

+100-63
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
#include "tcpclient.hpp"
22

3+
class TcpClient::TcpClientPrivate
4+
{
5+
public:
6+
explicit TcpClientPrivate(TcpClient *q)
7+
: q_ptr(q)
8+
{}
9+
10+
TcpClient *q_ptr;
11+
12+
std::string host = {};
13+
int port = 0;
14+
15+
CURL *curl = nullptr;
16+
CURLcode res = CURLE_OK;
17+
18+
bool connected = false;
19+
};
20+
321
TcpClient::TcpClient()
4-
: m_host()
5-
, m_port(0)
6-
, m_curl(nullptr)
7-
, m_res(CURLE_OK)
8-
, m_connected(false)
22+
: d_ptr(std::make_unique<TcpClientPrivate>(this))
923
{}
1024

1125
TcpClient::TcpClient(const std::string &host, int port)
12-
: m_host(host)
13-
, m_port(port)
14-
, m_curl(nullptr)
15-
, m_res(CURLE_OK)
16-
, m_connected(false)
17-
{}
26+
: d_ptr(std::make_unique<TcpClientPrivate>(this))
27+
{
28+
d_ptr->host = host;
29+
d_ptr->port = port;
30+
}
1831

1932
TcpClient::~TcpClient()
2033
{
@@ -23,110 +36,110 @@ TcpClient::~TcpClient()
2336

2437
auto TcpClient::connect() -> bool
2538
{
26-
if (m_connected) {
39+
if (d_ptr->connected) {
2740
return true;
2841
}
2942

30-
m_curl = curl_easy_init();
31-
if (m_curl == nullptr) {
43+
d_ptr->curl = curl_easy_init();
44+
if (d_ptr->curl == nullptr) {
3245
return false;
3346
}
3447

35-
curl_easy_setopt(m_curl, CURLOPT_URL, m_host.c_str());
36-
curl_easy_setopt(m_curl, CURLOPT_PORT, m_port);
37-
curl_easy_setopt(m_curl, CURLOPT_TCP_NODELAY, 1L);
38-
curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L);
39-
curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPIDLE, 120L);
40-
curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPINTVL, 60L);
48+
curl_easy_setopt(d_ptr->curl, CURLOPT_URL, d_ptr->host.c_str());
49+
curl_easy_setopt(d_ptr->curl, CURLOPT_PORT, d_ptr->port);
50+
curl_easy_setopt(d_ptr->curl, CURLOPT_TCP_NODELAY, 1L);
51+
curl_easy_setopt(d_ptr->curl, CURLOPT_TCP_KEEPALIVE, 1L);
52+
curl_easy_setopt(d_ptr->curl, CURLOPT_TCP_KEEPIDLE, 120L);
53+
curl_easy_setopt(d_ptr->curl, CURLOPT_TCP_KEEPINTVL, 60L);
4154

42-
m_res = curl_easy_perform(m_curl);
43-
if (m_res != CURLE_OK) {
44-
curl_easy_cleanup(m_curl);
45-
m_curl = nullptr;
55+
d_ptr->res = curl_easy_perform(d_ptr->curl);
56+
if (d_ptr->res != CURLE_OK) {
57+
curl_easy_cleanup(d_ptr->curl);
58+
d_ptr->curl = nullptr;
4659
return false;
4760
}
4861

49-
m_connected = true;
50-
return m_connected;
62+
d_ptr->connected = true;
63+
return d_ptr->connected;
5164
}
5265

5366
auto TcpClient::connect(const std::string &host, int port) -> bool
5467
{
55-
m_host = host;
56-
m_port = port;
68+
d_ptr->host = host;
69+
d_ptr->port = port;
5770
return connect();
5871
}
5972

6073
void TcpClient::disconnect()
6174
{
62-
if (!m_connected) {
75+
if (!d_ptr->connected) {
6376
return;
6477
}
6578

66-
curl_easy_cleanup(m_curl);
67-
m_curl = nullptr;
68-
m_connected = false;
79+
curl_easy_cleanup(d_ptr->curl);
80+
d_ptr->curl = nullptr;
81+
d_ptr->connected = false;
6982
}
7083

7184
void TcpClient::send(const std::string &data)
7285
{
73-
if (!m_connected) {
86+
if (!d_ptr->connected) {
7487
return;
7588
}
7689

77-
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, data.c_str());
78-
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, data.size());
90+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDS, data.c_str());
91+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDSIZE, data.size());
7992

80-
m_res = curl_easy_perform(m_curl);
81-
if (m_res != CURLE_OK) {
93+
d_ptr->res = curl_easy_perform(d_ptr->curl);
94+
if (d_ptr->res != CURLE_OK) {
8295
disconnect();
8396
return;
8497
}
8598
}
8699

87100
void TcpClient::send(const std::vector<char> &data)
88101
{
89-
if (!m_connected) {
102+
if (!d_ptr->connected) {
90103
return;
91104
}
92105

93-
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, data.data());
94-
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, data.size());
106+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDS, data.data());
107+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDSIZE, data.size());
95108

96-
m_res = curl_easy_perform(m_curl);
97-
if (m_res != CURLE_OK) {
109+
d_ptr->res = curl_easy_perform(d_ptr->curl);
110+
if (d_ptr->res != CURLE_OK) {
98111
disconnect();
99112
return;
100113
}
101114
}
102115

103116
void TcpClient::send(const char *data, size_t size)
104117
{
105-
if (!m_connected) {
118+
if (!d_ptr->connected) {
106119
return;
107120
}
108121

109-
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, data);
110-
curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, size);
122+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDS, data);
123+
curl_easy_setopt(d_ptr->curl, CURLOPT_POSTFIELDSIZE, size);
111124

112-
m_res = curl_easy_perform(m_curl);
113-
if (m_res != CURLE_OK) {
125+
d_ptr->res = curl_easy_perform(d_ptr->curl);
126+
if (d_ptr->res != CURLE_OK) {
114127
disconnect();
115128
return;
116129
}
117130
}
118131

119132
auto TcpClient::recv() -> std::string
120133
{
121-
if (!m_connected) {
134+
if (!d_ptr->connected) {
122135
return std::string();
123136
}
124137

125138
std::string data;
126-
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &data);
139+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEDATA, &data);
127140

128-
m_res = curl_easy_perform(m_curl);
129-
if (m_res != CURLE_OK) {
141+
d_ptr->res = curl_easy_perform(d_ptr->curl);
142+
if (d_ptr->res != CURLE_OK) {
130143
disconnect();
131144
return std::string();
132145
}
@@ -136,16 +149,16 @@ auto TcpClient::recv() -> std::string
136149

137150
auto TcpClient::recv(size_t size) -> std::string
138151
{
139-
if (!m_connected) {
152+
if (!d_ptr->connected) {
140153
return std::string();
141154
}
142155

143156
std::string data;
144157
data.resize(size);
145-
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &data);
158+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEDATA, &data);
146159

147-
m_res = curl_easy_perform(m_curl);
148-
if (m_res != CURLE_OK) {
160+
d_ptr->res = curl_easy_perform(d_ptr->curl);
161+
if (d_ptr->res != CURLE_OK) {
149162
disconnect();
150163
return std::string();
151164
}
@@ -155,14 +168,14 @@ auto TcpClient::recv(size_t size) -> std::string
155168

156169
auto TcpClient::recv(std::vector<char> &data) -> size_t
157170
{
158-
if (!m_connected) {
171+
if (!d_ptr->connected) {
159172
return 0;
160173
}
161174

162-
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &data);
175+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEDATA, &data);
163176

164-
m_res = curl_easy_perform(m_curl);
165-
if (m_res != CURLE_OK) {
177+
d_ptr->res = curl_easy_perform(d_ptr->curl);
178+
if (d_ptr->res != CURLE_OK) {
166179
disconnect();
167180
return 0;
168181
}
@@ -172,17 +185,41 @@ auto TcpClient::recv(std::vector<char> &data) -> size_t
172185

173186
auto TcpClient::recv(char *data, size_t size) -> size_t
174187
{
175-
if (!m_connected) {
188+
if (!d_ptr->connected) {
176189
return 0;
177190
}
178191

179-
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, data);
192+
curl_easy_setopt(d_ptr->curl, CURLOPT_WRITEDATA, data);
180193

181-
m_res = curl_easy_perform(m_curl);
182-
if (m_res != CURLE_OK) {
194+
d_ptr->res = curl_easy_perform(d_ptr->curl);
195+
if (d_ptr->res != CURLE_OK) {
183196
disconnect();
184197
return 0;
185198
}
186199

187200
return size;
188201
}
202+
203+
auto TcpClient::getHost() const -> std::string
204+
{
205+
return d_ptr->host;
206+
}
207+
auto TcpClient::getPort() const -> int
208+
{
209+
return d_ptr->port;
210+
}
211+
212+
auto TcpClient::isConnected() const -> bool
213+
{
214+
return d_ptr->connected;
215+
}
216+
217+
auto TcpClient::getLastError() const -> CURLcode
218+
{
219+
return d_ptr->res;
220+
}
221+
222+
auto TcpClient::getLastErrorString() const -> std::string
223+
{
224+
return curl_easy_strerror(d_ptr->res);
225+
}

‎Curl/tcpclient.hpp

+9-16
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
#include <curl/curl.h>
66

7+
#include <memory>
78
#include <string>
89
#include <vector>
910

1011
class TcpClient : noncopyable
1112
{
1213
public:
1314
TcpClient();
14-
TcpClient(const std::string &host, int port);
15+
explicit TcpClient(const std::string &host, int port);
1516
~TcpClient();
1617

1718
auto connect() -> bool;
@@ -27,23 +28,15 @@ class TcpClient : noncopyable
2728
auto recv(std::vector<char> &data) -> size_t;
2829
auto recv(char *data, size_t size) -> size_t;
2930

30-
[[nodiscard]] auto getHost() const -> std::string { return m_host; }
31-
[[nodiscard]] auto getPort() const -> int { return m_port; }
31+
[[nodiscard]] auto getHost() const -> std::string;
32+
[[nodiscard]] auto getPort() const -> int;
3233

33-
[[nodiscard]] auto isConnected() const -> bool { return m_connected; }
34+
[[nodiscard]] auto isConnected() const -> bool;
3435

35-
[[nodiscard]] auto getLastError() const -> CURLcode { return m_res; }
36-
[[nodiscard]] auto getLastErrorString() const -> std::string
37-
{
38-
return curl_easy_strerror(m_res);
39-
}
36+
[[nodiscard]] auto getLastError() const -> CURLcode;
37+
[[nodiscard]] auto getLastErrorString() const -> std::string;
4038

4139
private:
42-
std::string m_host;
43-
int m_port;
44-
45-
CURL *m_curl;
46-
CURLcode m_res;
47-
48-
bool m_connected;
40+
class TcpClientPrivate;
41+
std::unique_ptr<TcpClientPrivate> d_ptr;
4942
};

0 commit comments

Comments
 (0)
Please sign in to comment.