Skip to content

Instantly share code, notes, and snippets.

@gamapat
Last active May 20, 2020 07:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gamapat/44f94ae56c80d27175da9f4f335af1f0 to your computer and use it in GitHub Desktop.
Save gamapat/44f94ae56c80d27175da9f4f335af1f0 to your computer and use it in GitHub Desktop.
libcurl file download examples
class CurlGlobalStateGuard
{
public:
CurlGlobalStateGuard() { curl_global_init(CURL_GLOBAL_DEFAULT); }
~CurlGlobalStateGuard() { curl_global_cleanup(); }
};
static CurlGlobalStateGuard handle_curl_state;
using EasyHandle = std::unique_ptr<CURL, std::function<void(CURL*)>>;
EasyHandle CreateEasyHandle()
{
auto curl = EasyHandle(curl_easy_init(), curl_easy_cleanup);
if (!curl)
{
throw std::runtime_error("Failed creating CURL easy object");
}
return curl;
}
std::list<EasyHandle> handles(3);
/* init easy stacks */
try
{
std::for_each(handles.begin(), handles.end(), [](auto& handle) {handle = CreateEasyHandle(); });
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
void set_ssl(CURL* curl)
{
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
/* ... */
for (auto& handle : handles)
{
set_ssl(handle.get());
}
namespace
{
size_t write_to_file(void* contents, size_t size, size_t nmemb, void* userp)
{
size_t realsize = size * nmemb;
auto file = reinterpret_cast<std::ofstream*>(userp);
file->write(reinterpret_cast<const char*>(contents), realsize);
return realsize;
}
}
void save_to_file(CURL* curl)
{
static std::ofstream file("downloaded_data.txt", std::ios::binary);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_file);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, reinterpret_cast<void*>(&file));
}
/* ... */
for (auto& handle : handles)
{
set_ssl(handle.get());
}
for (auto& handle : handles)
{
/* Perform the request, res will get the return code */
auto res = curl_easy_perform(handle.get());
/* Check for errors */
if (res != CURLE_OK)
{
std::cerr << "curl_easy_perform() failed:" <<
curl_easy_strerror(res) << std::endl;
return -1;
}
}
int download_synchronous(void)
{
std::list<EasyHandle> handles(3);
/* init easy stacks */
try
{
std::for_each(handles.begin(), handles.end(), [](auto& handle) {handle = CreateEasyHandle(); });
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
for (auto& handle : handles)
{
/* set options */
curl_easy_setopt(handle.get(), CURLOPT_URL, "https://raw.githubusercontent.com/curl/curl/master/docs/examples/https.c");
set_ssl(handle.get());
save_to_file(handle.get());
/* Perform the request, res will get the return code */
auto res = curl_easy_perform(handle.get());
/* Check for errors */
if (res != CURLE_OK)
{
std::cerr << "curl_easy_perform() failed:" <<
curl_easy_strerror(res) << std::endl;
return -1;
}
}
return 0;
}
using MultiHandle = std::unique_ptr<CURLM, std::function<void(CURLM*)>>;
MultiHandle CreateMultiHandle()
{
auto curl = MultiHandle(curl_multi_init(), curl_multi_cleanup);
if (!curl)
{
throw std::runtime_error("Failed creating CURL multi object");
}
return curl;
}
std::list<EasyHandle> handles(3);
MultiHandle multi_handle;
/* init easy and multi stacks */
try
{
multi_handle = CreateMultiHandle();
std::for_each(handles.begin(), handles.end(), [](auto& handle){handle = CreateEasyHandle(); });
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
/* set options */
std::for_each(handles.begin(), handles.end(), [](auto& handle) {
curl_easy_setopt(handle.get(), CURLOPT_URL, "https://raw.githubusercontent.com/curl/curl/master/docs/examples/multi-double.c");
set_ssl(handle.get());
save_to_file(handle.get());
});
/* add the individual transfers */
std::for_each(handles.begin(), handles.end(), [&multi_handle](auto& handle) {curl_multi_add_handle(multi_handle.get(), handle.get()); });
int still_running = 0; /* keep number of running handles */
/* we start some action by calling perform right away */
curl_multi_perform(multi_handle, &still_running);
while (still_running) {
/*...*/
}
timeval get_timeout(CURLM* multi_handle)
{
long curl_timeo = -1;
/* set a suitable timeout to play around with */
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
curl_multi_timeout(multi_handle, &curl_timeo);
if (curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if (timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (curl_timeo % 1000) * 1000;
}
return timeout;
}
/*...*/
while (still_running) {
struct timeval timeout = get_timeout(multi_handle);
/*...*/
}
int wait_if_needed(CURLM* multi_handle, timeval& timeout)
{
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
int maxfd = -1;
/* get file descriptors from the transfers */
auto mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
if (mc != CURLM_OK) {
std::cerr << "curl_multi_fdset() failed, code " << mc << "." << std::endl;
}
/* On success the value of maxfd is guaranteed to be >= -1. We call
sleep for 100ms, which is the minimum suggested value in the
curl_multi_fdset() doc. */
if (maxfd == -1) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
int rc = maxfd != -1 ? select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout) : 0;
return rc;
}
/*...*/
while (still_running) {
/*...*/
auto rc = wait_if_needed(multi_handle, timeout);
/*...*/
}
while (still_running) {
/*...*/
auto rc = wait_if_needed(multi_handle, timeout);
if (rc >= 0)
{
/* timeout or readable/writable sockets */
curl_multi_perform(multi_handle, &still_running);
}
}
namespace
{
timeval get_timeout(CURLM* multi_handle)
{
long curl_timeo = -1;
/* set a suitable timeout to play around with */
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
curl_multi_timeout(multi_handle, &curl_timeo);
if (curl_timeo >= 0) {
timeout.tv_sec = curl_timeo / 1000;
if (timeout.tv_sec > 1)
timeout.tv_sec = 1;
else
timeout.tv_usec = (curl_timeo % 1000) * 1000;
}
return timeout;
}
int wait_if_needed(CURLM* multi_handle, timeval& timeout)
{
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
int maxfd = -1;
/* get file descriptors from the transfers */
auto mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
if (mc != CURLM_OK) {
std::cerr << "curl_multi_fdset() failed, code " << mc << "." << std::endl;
}
/* On success the value of maxfd is guaranteed to be >= -1. We call
sleep for 100ms, which is the minimum suggested value in the
curl_multi_fdset() doc. */
if (maxfd == -1) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
int rc = maxfd != -1 ? select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout) : 0;
return rc;
}
}
void multi_loop(CURLM* multi_handle)
{
int still_running = 0; /* keep number of running handles */
/* we start some action by calling perform right away */
curl_multi_perform(multi_handle, &still_running);
while (still_running) {
struct timeval timeout = get_timeout(multi_handle);
auto rc = wait_if_needed(multi_handle, timeout);
if (rc >= 0)
{
/* timeout or readable/writable sockets */
curl_multi_perform(multi_handle, &still_running);
}
/* else select error */
}
}
multi_loop(multi_handle.get());
std::for_each(handles.begin(), handles.end(), [&multi_handle](auto& handle) {curl_multi_remove_handle(multi_handle.get(), handle.get()); });
int download_asynchronous(void)
{
std::list<EasyHandle> handles(3);
MultiHandle multi_handle;
/* init easy and multi stacks */
try
{
multi_handle = CreateMultiHandle();
std::for_each(handles.begin(), handles.end(), [](auto& handle){handle = CreateEasyHandle(); });
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
/* set options */
std::for_each(handles.begin(), handles.end(), [](auto& handle) {
curl_easy_setopt(handle.get(), CURLOPT_URL, "https://raw.githubusercontent.com/curl/curl/master/docs/examples/multi-double.c");
set_ssl(handle.get());
save_to_file(handle.get());
});
/* add the individual transfers */
std::for_each(handles.begin(), handles.end(), [&multi_handle](auto& handle) {curl_multi_add_handle(multi_handle.get(), handle.get()); });
multi_loop(multi_handle.get());
std::for_each(handles.begin(), handles.end(), [&multi_handle](auto& handle) {curl_multi_remove_handle(multi_handle.get(), handle.get()); });
return 0;
}
/*...*/
for(auto& handle : handles)
{
/* HTTP/2 please */
curl_easy_setopt(handle.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
/* wait for pipe connection to confirm */
curl_easy_setopt(handle.get(), CURLOPT_PIPEWAIT, 1L);
}
curl_multi_setopt(multi_handle.get(), CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
/*
* Download many transfers over HTTP/2, using the same connection!
*/
int download_multiplexing(void)
{
std::list<EasyHandle> handles(3);
MultiHandle multi_handle;
/* init easy and multi stacks */
try
{
multi_handle = CreateMultiHandle();
for(auto& handle : handles)
{
handle = CreateEasyHandle();
/* HTTP/2 please */
curl_easy_setopt(handle.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
/* wait for pipe connection to confirm */
curl_easy_setopt(handle.get(), CURLOPT_PIPEWAIT, 1L);
}
}
catch (const std::exception& ex)
{
std::cerr << ex.what() << std::endl;
return -1;
}
for (auto& handle : handles)
{
curl_easy_setopt(handle.get(), CURLOPT_URL, "https://raw.githubusercontent.com/curl/curl/master/docs/examples/http2-download.c");
set_ssl(handle.get());
save_to_file(handle.get());
/* add the individual transfers */
curl_multi_add_handle(multi_handle.get(), handle.get());
}
curl_multi_setopt(multi_handle.get(), CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
multi_loop(multi_handle.get());
std::for_each(handles.begin(), handles.end(), [&multi_handle](auto& handle) {curl_multi_remove_handle(multi_handle.get(), handle.get()); });
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment