Files
kwa.fr/sdi_toolBox_1.0.x/toolBox/sdi_toolBox/comm/curlHttpClient.h
2026-03-12 16:31:18 +01:00

431 lines
14 KiB
C++

/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <curl/curl.h>
#include <filesystem>
#include <memory>
#include <mutex>
#include <ostream>
#include <sdi_toolBox/logs/reLog.h>
#include <string>
#include <unordered_map>
namespace sdi_ToolBox::communication
{
//--------------------------------------------------------------
class CurlGlobalInitializer
{
public:
static void init() // Initialize libcurl globally
{
static auto instance = std::unique_ptr<CurlGlobalInitializer>(new CurlGlobalInitializer());
}
public:
virtual ~CurlGlobalInitializer() // Default destructor
{
curl_global_cleanup();
}
CurlGlobalInitializer(const CurlGlobalInitializer &obj) = delete; // Copy constructor
CurlGlobalInitializer(CurlGlobalInitializer &&obj) noexcept = delete; // Move constructor
CurlGlobalInitializer &operator=(const CurlGlobalInitializer &obj) = delete; // Copy assignment operator
CurlGlobalInitializer &operator=(CurlGlobalInitializer &&obj) noexcept = delete; // Move assignment operator
protected:
CurlGlobalInitializer() // Default constructor
{
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
throw std::runtime_error("ERROR: Failed to initialize cURL");
}
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
class CurlHttpClient
{
public:
enum class Method : uint8_t
{
http_GET,
http_POST,
http_PUT,
http_DELETE,
};
struct Settings
{
bool enableSSL = true;
bool ssl_verifyPeer = true;
bool ssl_verifyHost = true;
std::filesystem::path ssl_caInfo;
std::string ssl_certBlob;
std::chrono::seconds timeout{ 10 };
bool operator==(const Settings &other) const
{
// Compare all fields against the other object
return (enableSSL == other.enableSSL &&
ssl_verifyPeer == other.ssl_verifyPeer &&
ssl_verifyHost == other.ssl_verifyHost &&
ssl_caInfo == other.ssl_caInfo &&
ssl_certBlob == other.ssl_certBlob);
}
};
using Headers = std::unordered_map<std::string, std::string>;
struct Request
{
std::string url;
Method method = Method::http_GET;
std::string body;
Headers headers;
Settings settings;
bool operator==(const Request &other) const
{
// Compare all fields against the other object
return (url == other.url &&
method == other.method &&
body == other.body &&
headers == other.headers &&
settings == other.settings);
}
};
struct Response
{
int returnCode = -1;
std::string content;
};
public:
CurlHttpClient(); // Default constructor
virtual ~CurlHttpClient() = default; // Default destructor
CurlHttpClient(const CurlHttpClient &obj) = delete; // Copy constructor
CurlHttpClient(CurlHttpClient &&obj) noexcept = delete; // Move constructor
CurlHttpClient &operator=(const CurlHttpClient &obj) = delete; // Copy assignment operator
CurlHttpClient &operator=(CurlHttpClient &&obj) noexcept = delete; // Move assignment operator
const Response &performRequest(const Request &request = {}); // Perform HTTP request
const Response &performRequest_nothrow(const Request &request = {}); // Perform HTTP request
const Request &getRequest() const; // Retrieves curl request
const Response &getResponse() const; // Retrieves curl response
protected:
mutable std::mutex m_mtx;
CURL *m_curl = nullptr;
curl_slist *m_headerList = nullptr;
Request m_request;
Response m_response;
private:
CURLcode setRequestOptions(); // Set cURL request options
static size_t write_callback(void *contents, // Callback for writing received data
size_t size,
size_t itemCount,
void *userData);
};
//--------------------------------------------------------------
/* Default constructor */
inline CurlHttpClient::CurlHttpClient()
{
CurlGlobalInitializer::init();
}
//--------------------------------------------------------------
/* Perform HTTP request */
inline const CurlHttpClient::Response &CurlHttpClient::performRequest(const Request &request)
{
std::lock_guard lock(m_mtx);
if (const Request default_req = {}; request != default_req)
m_request = request;
try
{
m_curl = curl_easy_init();
if (!m_curl)
throw std::runtime_error("unable to initialize curl library");
// Set request options
if (const auto res = setRequestOptions(); res != CURLE_OK)
throw std::runtime_error(std::format("ERROR: Failed to set up CURL options: {}", curl_easy_strerror(res)));
// Perform request
if (const auto res = curl_easy_perform(m_curl); res != CURLE_OK)
throw std::runtime_error(std::format("ERROR: curl_easy_perform() failed: {}", curl_easy_strerror(res)));
if (const auto res = curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &m_response.returnCode); res != CURLE_OK)
throw std::runtime_error(std::format("ERROR: Failed to get content code: {}", curl_easy_strerror(res)));
}
catch (const std::exception &e)
{
m_response.returnCode = -1;
m_response.content = e.what();
}
// Free resources
if (m_curl)
{
curl_easy_cleanup(m_curl); // free the easy curl handle
m_curl = nullptr;
}
if (m_headerList)
{
curl_slist_free_all(m_headerList); // free the custom headers
m_curl = nullptr;
}
if (m_response.returnCode == -1)
throw std::runtime_error(m_response.content);
return m_response;
}
//--------------------------------------------------------------
/* Perform HTTP request */
inline const CurlHttpClient::Response &CurlHttpClient::performRequest_nothrow(const Request &request)
{
try
{
performRequest(request);
}
catch (const std::exception &e)
{
LogError() << e.what() << std::endl;
}
return m_response;
}
//--------------------------------------------------------------
/* Retrieves curl request */
inline const CurlHttpClient::Request &CurlHttpClient::getRequest() const
{
std::lock_guard lock(m_mtx);
return m_request;
}
//--------------------------------------------------------------
/* Retrieves curl response */
inline const CurlHttpClient::Response &CurlHttpClient::getResponse() const
{
std::lock_guard lock(m_mtx);
return m_response;
}
//--------------------------------------------------------------
/* Set cURL request options */
inline CURLcode CurlHttpClient::setRequestOptions()
{
// --- Common Options ---
// Set the target URL
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_URL, m_request.url.c_str()); res != CURLE_OK)
return res;
// Set the write callback function
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, write_callback); res != CURLE_OK)
return res;
// Set the user data pointer for the callback (our content buffer)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &m_response.content); res != CURLE_OK)
return res;
// Set custom headers if any
if (m_headerList)
{
// Prepare header list
for (const auto &[key, value] : m_request.headers)
{
const auto header = format("{0:}: {1:}", key, value);
m_headerList = curl_slist_append(m_headerList, header.c_str());
}
// Append headers to the request
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headerList); res != CURLE_OK)
return res;
}
// Set the max time in seconds for the entire operation (including connection and transfer)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, m_request.settings.timeout.count()); res != CURLE_OK)
return res;
// --- SSL/TLS Options ---
// curl documentation recommends setting both for full control
if (m_request.settings.enableSSL)
{
// Enable peer certificate verification
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); res != CURLE_OK)
return res;
// Enable host name verification (prevents 'Man-in-the-middle' attacks)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 2L); res != CURLE_OK)
return res;
if (!m_request.settings.ssl_caInfo.empty())
{
// Optionally, set the path to a CA bundle file (often not needed
// if curl is built with a system-wide CA store)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CAINFO, m_request.settings.ssl_caInfo.c_str()); res != CURLE_OK)
return res;
}
else if (!m_request.settings.ssl_certBlob.empty())
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSLKEY_BLOB, &m_request.settings.ssl_certBlob); res != CURLE_OK)
return res;
}
}
else
{
// Disable verification (USE WITH CAUTION: Insecure!)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); res != CURLE_OK)
return res;
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0L); res != CURLE_OK)
return res;
}
// --- Method-Specific Options ---
switch (m_request.method)
{
using enum Method;
case http_GET:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_HTTPGET, 1L); res != CURLE_OK)
return res;
break;
}
case http_POST:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POST, 1L); res != CURLE_OK)
return res;
// Set the data to be sent
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_request.body.c_str()); res != CURLE_OK)
return res;
break;
}
case http_PUT:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_PUT, 1L); res != CURLE_OK)
return res;
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "PUT"); res != CURLE_OK)
return res;
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_request.body.c_str()); res != CURLE_OK)
return res;
break;
}
case http_DELETE:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); res != CURLE_OK)
return res;
break;
}
}
return CURLE_OK;
}
//--------------------------------------------------------------
/* Callback for writing received data */
inline size_t CurlHttpClient::write_callback(void *contents, const size_t size, const size_t itemCount, void *userData)
{
// Total size of the incoming buffer
const auto realSize = size * itemCount;
// Append the received data to the content string
const auto &response = static_cast<std::string *>(userData);
response->append(static_cast<char *>(contents), realSize);
return realSize;
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
class CurlInfo
{
public:
CurlInfo(); // Default constructor
virtual ~CurlInfo() = default; // Default destructor
CurlInfo(const CurlInfo &obj) = delete; // Copy constructor
CurlInfo(CurlInfo &&obj) noexcept = delete; // Move constructor
CurlInfo &operator=(const CurlInfo &obj) = delete; // Copy assignment operator
CurlInfo &operator=(CurlInfo &&obj) noexcept = delete; // Move assignment operator
void toStream(std::ostream &stream); // Print cURL information to stream object
void print(); // Print cURL information to cout
std::string version;
uint32_t features;
bool ssl_support = false;
std::string ssl_version;
std::vector<std::string> availableProtocols;
};
//--------------------------------------------------------------
/* Default constructor */
inline CurlInfo::CurlInfo()
{
CurlGlobalInitializer::init();
// Retrieves information from the library
const auto info = curl_version_info(CURLVERSION_NOW);
version = info->version;
features = info->features;
ssl_support = info->features & CURL_VERSION_SSL;
ssl_version = info->ssl_version;
if (info->protocols)
{
for (const char *const *proto = info->protocols; *proto; ++proto)
availableProtocols.emplace_back(*proto);
}
}
//--------------------------------------------------------------
/* Print cURL information to stream object */
inline void CurlInfo::toStream(std::ostream &stream)
{
stream << "--- libcurl Version Info ---" << "\n";
stream << "Version : " << version << "\n";
stream << "Features (bitmask) : " << std::format("0x{0:X}", features) << "\n";
stream << "SSL/TLS support : " << ((ssl_support) ? std::format("**Yes** [{0:}]", ssl_version) : "No") << "\n";
if (!availableProtocols.empty())
{
stream << "Available protocols: ";
bool first = true;
for (const auto protocol : availableProtocols)
{
if (!first)
stream << ", ";
stream << protocol;
first = false;
}
stream << "\n";
}
}
//--------------------------------------------------------------
/* Print cURL information to cout */
inline void CurlInfo::print()
{
toStream(std::cout);
std::cout << std::flush;
}
//--------------------------------------------------------------
} // namespace sdi_ToolBox::communication