CCEngine/CCEngine/HTTPClient.cpp
2018-03-18 12:36:11 +01:00

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);
}