Adds the sdi_toolBox library (temporary version)
This commit is contained in:
430
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/comm/curlHttpClient.h
Normal file
430
sdi_toolBox_1.0.x/toolBox/sdi_toolBox/comm/curlHttpClient.h
Normal file
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
{{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
|
||||
Reference in New Issue
Block a user