313 lines
8.3 KiB
C++
313 lines
8.3 KiB
C++
#include "HTTPClient.h"
|
|
|
|
#include <curl/curl.h>
|
|
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
|
|
namespace
|
|
{
|
|
|
|
static
|
|
void dump(const char *text,
|
|
FILE *stream, unsigned char *ptr, size_t size)
|
|
{
|
|
size_t i;
|
|
size_t c;
|
|
unsigned int width = 0x10;
|
|
|
|
fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n",
|
|
text, (long)size, (long)size);
|
|
|
|
fwrite(ptr, 1, size, stream);
|
|
fputc('\n', stream); // newline
|
|
/* for (i = 0; i<size; i += width) {
|
|
fprintf(stream, "%4.4lx: ", (long)i);
|
|
|
|
// show hex to the left
|
|
for (c = 0; c < width; c++) {
|
|
if (i + c < size)
|
|
fprintf(stream, "%02x ", ptr[i + c]);
|
|
else
|
|
fputs(" ", stream);
|
|
}
|
|
|
|
// show data on the right
|
|
for (c = 0; (c < width) && (i + c < size); c++) {
|
|
char x = (ptr[i + c] >= 0x20 && ptr[i + c] < 0x80) ? ptr[i + c] : '.';
|
|
fputc(x, stream);
|
|
}
|
|
|
|
fputc('\n', stream); // newline
|
|
} */
|
|
}
|
|
|
|
static
|
|
int my_trace(CURL *handle, curl_infotype type,
|
|
char *data, size_t size,
|
|
void *userp)
|
|
{
|
|
const char *text;
|
|
(void)handle; /* prevent compiler warning */
|
|
(void)userp;
|
|
|
|
switch (type) {
|
|
case CURLINFO_TEXT:
|
|
fprintf(stderr, "== Info: %s", data);
|
|
default: /* in case a new one is introduced to shock us */
|
|
return 0;
|
|
|
|
case CURLINFO_HEADER_OUT:
|
|
text = "=> Send header";
|
|
break;
|
|
case CURLINFO_DATA_OUT:
|
|
text = "=> Send data";
|
|
break;
|
|
case CURLINFO_SSL_DATA_OUT:
|
|
text = "=> Send SSL data";
|
|
break;
|
|
case CURLINFO_HEADER_IN:
|
|
text = "<= Recv header";
|
|
break;
|
|
case CURLINFO_DATA_IN:
|
|
text = "<= Recv data";
|
|
break;
|
|
case CURLINFO_SSL_DATA_IN:
|
|
text = "<= Recv SSL data";
|
|
break;
|
|
}
|
|
|
|
dump(text, stderr, (unsigned char *)data, size);
|
|
return 0;
|
|
}
|
|
|
|
static size_t reader(char *ptr, size_t size, size_t nmemb, std::istream *is)
|
|
{
|
|
std::streamsize totalRead = 0;
|
|
if (*is)
|
|
{
|
|
is->read(&ptr[totalRead], size * nmemb - totalRead);
|
|
// is->read(&ptr[totalRead], 1);
|
|
totalRead = is->gcount();
|
|
}
|
|
|
|
return totalRead;
|
|
}
|
|
|
|
static int writer(char *data, size_t size, size_t nmemb, std::ostream *os)
|
|
{
|
|
if (os == NULL)
|
|
return 0;
|
|
|
|
os->write(data, size*nmemb);
|
|
|
|
return size * nmemb;
|
|
}
|
|
|
|
struct CURLDeleter
|
|
{
|
|
void operator() (CURL* ptr)
|
|
{
|
|
if (ptr)
|
|
{
|
|
curl_easy_cleanup(ptr);
|
|
}
|
|
}
|
|
};
|
|
|
|
struct curl_slist_deleter
|
|
{
|
|
void operator() (curl_slist* ptr)
|
|
{
|
|
if (ptr)
|
|
{
|
|
curl_slist_free_all(ptr);
|
|
}
|
|
}
|
|
};
|
|
|
|
struct DownloadSession
|
|
{
|
|
char errorBuffer[CURL_ERROR_SIZE] = { 0 };
|
|
std::unique_ptr<CURL, CURLDeleter> conn;
|
|
std::unique_ptr<curl_slist, curl_slist_deleter> extraHeaders;
|
|
};
|
|
|
|
DownloadSession initCurlRequest(const std::string& url, std::ostream &os)
|
|
{
|
|
DownloadSession result;
|
|
CURLcode code;
|
|
|
|
std::unique_ptr<CURL, CURLDeleter> conn(curl_easy_init());
|
|
|
|
if (!conn)
|
|
{
|
|
throw std::runtime_error("Failed to create CURL connection");
|
|
}
|
|
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_ERRORBUFFER, result.errorBuffer);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set error buffer [" << code << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
const char* errorBuffer = result.errorBuffer;
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_URL, url.c_str());
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set URL [" << errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_FOLLOWLOCATION, 1L);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set redirect option [" << errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_MAXREDIRS, 5L);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set maximum number of redirects [" << errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_WRITEFUNCTION, writer);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set write function [" << errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_WRITEDATA, &os);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set write data [" << errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
// DEBUG:
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_DEBUGFUNCTION, my_trace);
|
|
code = curl_easy_setopt(conn.get(), CURLOPT_VERBOSE, 1);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set verbose mode";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
result.conn = std::move(conn);
|
|
|
|
return result;
|
|
}
|
|
|
|
DownloadSession initCurlPostJsonRequest(const std::string& url, std::istream& is, size_t length, std::ostream &os)
|
|
{
|
|
DownloadSession result = initCurlRequest(url, os);
|
|
CURLcode code;
|
|
|
|
auto contentLengthHeader = std::string("Content-Length: ") + std::to_string(length);
|
|
|
|
struct curl_slist *headers = NULL;
|
|
headers = curl_slist_append(headers, "Accept: application/json");
|
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
headers = curl_slist_append(headers, contentLengthHeader.c_str());
|
|
headers = curl_slist_append(headers, "Charsets: utf-8");
|
|
result.extraHeaders.reset(headers);
|
|
|
|
code = curl_easy_setopt(result.conn.get(), CURLOPT_POST, 1L);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set post option [" << result.errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
code = curl_easy_setopt(result.conn.get(), CURLOPT_HTTPHEADER, result.extraHeaders.get());
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set http headers [" << result.errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
code = curl_easy_setopt(result.conn.get(), CURLOPT_READFUNCTION, reader);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set read function [" << result.errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
code = curl_easy_setopt(result.conn.get(), CURLOPT_READDATA, &is);
|
|
if (code != CURLE_OK)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Failed to set read data [" << result.errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
HTTPClient::HTTPClient()
|
|
{
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
}
|
|
|
|
void HTTPClient::get(const std::string& url, std::ostream& dstStream)
|
|
{
|
|
auto session = initCurlRequest(url, dstStream);
|
|
|
|
auto code = curl_easy_perform(session.conn.get());
|
|
session.conn.release();
|
|
|
|
if (code != CURLE_OK)
|
|
{
|
|
if (code == CURLE_COULDNT_CONNECT)
|
|
{
|
|
throw CouldNotConnectException();
|
|
}
|
|
|
|
std::ostringstream oss;
|
|
oss << "Failed to get '" << url << "' [" << session.errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
}
|
|
|
|
void HTTPClient::postJson(const std::string& url, std::istream& json, size_t length, std::ostream& dstStream)
|
|
{
|
|
auto session = initCurlPostJsonRequest(url, json, length, dstStream);
|
|
|
|
auto code = curl_easy_perform(session.conn.get());
|
|
session.conn.release();
|
|
|
|
if (code != CURLE_OK)
|
|
{
|
|
if (code == CURLE_COULDNT_CONNECT)
|
|
{
|
|
throw CouldNotConnectException();
|
|
}
|
|
std::ostringstream oss;
|
|
oss << "Failed to post '" << url << "' [" << session.errorBuffer << "]";
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
}
|
|
|
|
void HTTPClient::postJson(const std::string& url, const std::string& json, std::ostream& dstStream)
|
|
{
|
|
std::istringstream is(json);
|
|
return postJson(url, is, json.size(), dstStream);
|
|
}
|