Я бы использовал пары FUNCTION/DATA
в классе-обертке, чтобы иметь возможность создавать обработчики событий, которые вызываются автоматически для событий, которые вы хотите обработать. Вот пример с комментариями в коде:
#include "curl/curl.h"
#include <iostream>
#include <memory>
#include <stdexcept>
#include <utility>
// A simple wrapper class for the C functions in libcurl
class EasyCurly {
public:
// default constructor
EasyCurly() : handle(curl_easy_init()) {
if(handle == nullptr) throw std::runtime_error("curl_easy_init failed");
// Set "this" as data pointer in callbacks to be able to make a call to the
// correct EasyCurly object. There are a lot more callback functions you
// could add here if you need them.
setopt(CURLOPT_WRITEDATA, this);
setopt(CURLOPT_DEBUGDATA, this);
setopt(CURLOPT_XFERINFODATA, this);
// Setup of proxy/callback functions. There should be one for each function
// above.
setopt(CURLOPT_WRITEFUNCTION, write_callback);
setopt(CURLOPT_DEBUGFUNCTION, debug_callback);
setopt(CURLOPT_XFERINFOFUNCTION, progress_callback);
// some default options, remove those you usually don't want
setopt(CURLOPT_NOPROGRESS, 0); // turn on progress callbacks
setopt(CURLOPT_FOLLOWLOCATION, 1L); // redirects
setopt(CURLOPT_HTTPPROXYTUNNEL, 1L); // corp. proxies etc.
// setopt(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
setopt(CURLOPT_VERBOSE, 1L); // we want it all
}
// copy constructor
EasyCurly(const EasyCurly& other) : handle(curl_easy_duphandle(other.handle)) {
if(handle == nullptr) throw std::runtime_error("curl_easy_duphandle failed");
// State information is not shared when using curl_easy_duphandle. Only the
// options you've set (so you can create one CURL object, set its options and
// then use as a template for other objects. The document and debug data are
// therefor also not copied.
}
// move constructor
EasyCurly(EasyCurly&& other) :
handle(std::exchange(other.handle, nullptr)),
m_document(std::move(other.m_document)), m_debug(std::move(other.m_debug)) {}
// copy assignment
EasyCurly& operator=(const EasyCurly& other) {
CURL* tmp_handle = curl_easy_duphandle(other.handle);
if(handle == nullptr) throw std::runtime_error("curl_easy_duphandle failed");
// dup succeeded, now destroy any handle we might have and copy the tmp
curl_easy_cleanup(handle);
handle = tmp_handle;
return *this;
}
// move assignment
EasyCurly& operator=(EasyCurly&& other) {
std::swap(handle, other.handle);
std::swap(m_document, other.m_document);
std::swap(m_debug, other.m_debug);
return *this;
}
virtual ~EasyCurly() { curl_easy_cleanup(handle); }
// To be able to use an instance of EasyCurly with C interfaces if you don't add
// a function to this class for it, this operator will help
operator CURL*() { return handle; }
// generic curl_easy_setopt wrapper
template<typename T>
CURLcode setopt(CURLoption option, T v) {
return curl_easy_setopt(handle, option, v);
}
// perform by supplying url
CURLcode perform(std::string_view url) {
setopt(CURLOPT_URL, url.data());
return perform();
}
// perform with a previously supplied url
CURLcode perform() {
m_document.clear();
m_debug.clear();
return curl_easy_perform(handle);
}
// get collected data
std::string const& document() const { return m_document; }
std::string const& debug() const { return m_debug; }
// callbacks from proxy functions
virtual size_t on_write(char* ptr, size_t total_size) {
m_document.insert(m_document.end(), ptr, ptr + total_size);
// std::cout << "<write_callback> size " << total_size << "\n";
return total_size;
}
virtual int on_debug(curl_infotype /*type*/, char* data, size_t size) {
m_debug.insert(m_debug.end(), data, data + size);
// std::cout << "<debug>\n";
return 0; // must return 0
}
// override this to make a progress bar ...
virtual int on_progress(curl_off_t /*dltotal*/, curl_off_t /*dlnow*/,
curl_off_t /*ultotal*/, curl_off_t /*ulnow*/) {
// std::cout << "<progress>\n";
return 0;
}
private:
// a private class to initialize and cleanup once
class CurlGlobalInit {
public:
CurlGlobalInit() {
// std::cout << "CurlGlobalInit\n";
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
if(res) throw std::runtime_error("curl_global_init failed");
}
~CurlGlobalInit() {
// std::cout << "~CurlGlobalInit\n";
curl_global_cleanup();
}
};
// callback functions - has to be static to work with the C interface in curl
// use the data pointer (this) that we set in the constructor and cast it back
// to a EasyCurly* and call the event handler in the correct object.
static size_t write_callback(char* ptr, size_t size, size_t nmemb,
void* userdata) {
EasyCurly* ecurly = static_cast<EasyCurly*>(userdata);
return ecurly->on_write(ptr, nmemb * size); // size==1 really
}
static int debug_callback(CURL* /*handle*/, curl_infotype type, char* data,
size_t size, void* userptr) {
EasyCurly* ecurly = static_cast<EasyCurly*>(userptr);
return ecurly->on_debug(type, data, size);
}
static int progress_callback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow) {
EasyCurly* ecurly = static_cast<EasyCurly*>(clientp);
return ecurly->on_progress(dltotal, dlnow, ultotal, ulnow);
}
// resources
CURL* handle;
std::string m_document{};
std::string m_debug{};
// a static initializer object
static CurlGlobalInit setup_and_teardown;
};
// This must be defined in your .cpp file if you use this and split it into header
// and implementation parts.
EasyCurly::CurlGlobalInit EasyCurly::setup_and_teardown{};
// end of wrapper class
// --------------------------------------------------------------------------
// with that in place, the rest becomes something like this:
int main() {
EasyCurly curl; // our curl object
CURLcode res = curl.perform("http://www.google.com/");
std::cout << "result: " << res << "\n";
std::cout << "--- document size " << curl.document().size() << " captured ---\n"
<< curl.document() << "---------\n\n";
std::cout << "--- debug size " << curl.debug().size() << " captured ---\n"
<< curl.debug() << "---------\n";
}