Skip to content

Commit 3bef4dc

Browse files
authored
Merge pull request #331 from ammarfaizi2/curl-keep-alive
Allow curl handle in `CurlHttpClient` to be kept alive
2 parents f88c332 + 775291f commit 3bef4dc

File tree

2 files changed

+35
-17
lines changed

2 files changed

+35
-17
lines changed

include/tgbot/net/CurlHttpClient.h

+10-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010

1111
#include <curl/curl.h>
1212

13+
#include <mutex>
1314
#include <string>
1415
#include <vector>
16+
#include <thread>
17+
#include <unordered_map>
1518

1619
namespace TgBot {
1720

@@ -35,9 +38,14 @@ class TGBOT_API CurlHttpClient : public HttpClient {
3538
std::string makeRequest(const Url& url, const std::vector<HttpReqArg>& args) const override;
3639

3740
/**
38-
* @brief Raw curl settings storage for fine tuning.
41+
* @brief Raw curl handles, each thread has its own handle.
3942
*/
40-
CURL* curlSettings;
43+
std::unordered_map<std::thread::id, CURL*> curlHandles;
44+
45+
/**
46+
* @brief Lock for curlHandles access.
47+
*/
48+
std::mutex curlHandlesMutex;
4149

4250
private:
4351
const HttpParser _httpParser;

src/net/CurlHttpClient.cpp

+25-15
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,33 @@
88
namespace TgBot {
99

1010
CurlHttpClient::CurlHttpClient() : _httpParser() {
11-
curlSettings = curl_easy_init();
12-
13-
curl_easy_setopt(curlSettings, CURLOPT_CONNECTTIMEOUT, 20);
14-
curl_easy_setopt(curlSettings, CURLOPT_TIMEOUT, _timeout);
1511
}
1612

1713
CurlHttpClient::~CurlHttpClient() {
18-
curl_easy_cleanup(curlSettings);
14+
std::lock_guard<std::mutex> lock(curlHandlesMutex);
15+
for (auto& c : curlHandles) {
16+
curl_easy_cleanup(c.second);
17+
}
18+
}
19+
20+
static CURL* getCurlHandle(const CurlHttpClient *c_) {
21+
CurlHttpClient *c = const_cast<CurlHttpClient *>(c_);
22+
23+
std::lock_guard<std::mutex> lock(c->curlHandlesMutex);
24+
auto id = std::this_thread::get_id();
25+
auto it = c->curlHandles.find(id);
26+
if (it == c->curlHandles.end()) {
27+
CURL* curl = curl_easy_init();
28+
if (!curl) {
29+
throw std::runtime_error("curl_easy_init() failed");
30+
}
31+
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 20);
32+
curl_easy_setopt(curl, CURLOPT_TIMEOUT, c->_timeout);
33+
c->curlHandles[id] = curl;
34+
return curl;
35+
}
36+
37+
return it->second;
1938
}
2039

2140
static std::size_t curlWriteString(char* ptr, std::size_t size, std::size_t nmemb, void* userdata) {
@@ -24,21 +43,14 @@ static std::size_t curlWriteString(char* ptr, std::size_t size, std::size_t nmem
2443
}
2544

2645
std::string CurlHttpClient::makeRequest(const Url& url, const std::vector<HttpReqArg>& args) const {
27-
// Copy settings for each call because we change CURLOPT_URL and other stuff.
28-
// This also protects multithreaded case.
29-
auto curl = curl_easy_duphandle(curlSettings);
46+
CURL* curl = getCurlHandle(this);
3047

3148
std::string u = url.protocol + "://" + url.host + url.path;
3249
if (args.empty()) {
3350
u += "?" + url.query;
3451
}
3552
curl_easy_setopt(curl, CURLOPT_URL, u.c_str());
3653

37-
// disable keep-alive
38-
struct curl_slist* headers = nullptr;
39-
headers = curl_slist_append(headers, "Connection: close");
40-
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
41-
4254
curl_mime* mime;
4355
curl_mimepart* part;
4456
mime = curl_mime_init(curl);
@@ -64,8 +76,6 @@ std::string CurlHttpClient::makeRequest(const Url& url, const std::vector<HttpRe
6476
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
6577

6678
auto res = curl_easy_perform(curl);
67-
curl_slist_free_all(headers);
68-
curl_easy_cleanup(curl);
6979
curl_mime_free(mime);
7080

7181
// If the request did not complete correctly, show the error

0 commit comments

Comments
 (0)