274 lines
6.9 KiB
C++
274 lines
6.9 KiB
C++
#ifndef NOHTTP
|
|
#include "Request.h" // includes curl.h, needs to come first to silence a warning on windows
|
|
#include "RequestManager.h"
|
|
|
|
#include <iostream>
|
|
|
|
const int curl_multi_wait_timeout_ms = 100;
|
|
const long curl_max_host_connections = 1;
|
|
const long curl_max_concurrent_streams = 50;
|
|
|
|
namespace http
|
|
{
|
|
const long timeout = 15;
|
|
ByteString proxy;
|
|
ByteString cafile;
|
|
ByteString capath;
|
|
ByteString user_agent;
|
|
|
|
void RequestManager::Shutdown()
|
|
{
|
|
if (rt_shutting_down)
|
|
return;
|
|
{
|
|
std::lock_guard<std::mutex> g(rt_mutex);
|
|
rt_shutting_down = true;
|
|
}
|
|
rt_cv.notify_one();
|
|
|
|
if (initialized)
|
|
{
|
|
worker_thread.join();
|
|
|
|
curl_multi_cleanup(multi);
|
|
multi = NULL;
|
|
curl_global_cleanup();
|
|
}
|
|
}
|
|
|
|
void RequestManager::Initialise(ByteString newProxy, ByteString newCafile, ByteString newCapath)
|
|
{
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
multi = curl_multi_init();
|
|
if (multi)
|
|
{
|
|
curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, curl_max_host_connections);
|
|
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 67, 0)
|
|
curl_multi_setopt(multi, CURLMOPT_MAX_CONCURRENT_STREAMS, curl_max_concurrent_streams);
|
|
#endif
|
|
}
|
|
|
|
proxy = newProxy;
|
|
cafile = newCafile;
|
|
capath = newCapath;
|
|
|
|
user_agent = ByteString::Build(
|
|
"PowderToy/", SAVE_VERSION, ".", MINOR_VERSION,
|
|
" (", IDENT_PLATFORM,
|
|
"; ", IDENT_BUILD,
|
|
"; M", MOD_ID,
|
|
"; ", IDENT,
|
|
") TPTPP/", SAVE_VERSION, ".", MINOR_VERSION, ".", BUILD_NUM, IDENT_RELTYPE, ".", SNAPSHOT_ID
|
|
);
|
|
|
|
worker_thread = std::thread([this]() { Worker(); });
|
|
initialized = true;
|
|
}
|
|
|
|
void RequestManager::Worker()
|
|
{
|
|
bool shutting_down = false;
|
|
while (!shutting_down)
|
|
{
|
|
{
|
|
std::unique_lock<std::mutex> l(rt_mutex);
|
|
if (!requests_added_to_multi)
|
|
{
|
|
while (!rt_shutting_down && requests_to_add.empty() && !requests_to_start && !requests_to_remove)
|
|
{
|
|
rt_cv.wait(l);
|
|
}
|
|
}
|
|
shutting_down = rt_shutting_down;
|
|
requests_to_remove = false;
|
|
requests_to_start = false;
|
|
for (Request *request : requests_to_add)
|
|
{
|
|
request->status = 0;
|
|
requests.insert(request);
|
|
}
|
|
requests_to_add.clear();
|
|
}
|
|
|
|
if (multi && requests_added_to_multi)
|
|
{
|
|
int dontcare;
|
|
struct CURLMsg *msg;
|
|
|
|
curl_multi_wait(multi, nullptr, 0, curl_multi_wait_timeout_ms, &dontcare);
|
|
curl_multi_perform(multi, &dontcare);
|
|
while ((msg = curl_multi_info_read(multi, &dontcare)))
|
|
{
|
|
if (msg->msg == CURLMSG_DONE)
|
|
{
|
|
Request *request;
|
|
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &request);
|
|
|
|
int finish_with = 600;
|
|
|
|
switch (msg->data.result)
|
|
{
|
|
case CURLE_OK:
|
|
long code;
|
|
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
|
|
finish_with = (int)code;
|
|
break;
|
|
|
|
case CURLE_UNSUPPORTED_PROTOCOL: finish_with = 601; break;
|
|
case CURLE_COULDNT_RESOLVE_HOST: finish_with = 602; break;
|
|
case CURLE_OPERATION_TIMEDOUT: finish_with = 605; break;
|
|
case CURLE_URL_MALFORMAT: finish_with = 606; break;
|
|
case CURLE_COULDNT_CONNECT: finish_with = 607; break;
|
|
case CURLE_COULDNT_RESOLVE_PROXY: finish_with = 608; break;
|
|
case CURLE_TOO_MANY_REDIRECTS: finish_with = 611; break;
|
|
|
|
case CURLE_SSL_CONNECT_ERROR: finish_with = 612; break;
|
|
case CURLE_SSL_ENGINE_NOTFOUND: finish_with = 613; break;
|
|
case CURLE_SSL_ENGINE_SETFAILED: finish_with = 614; break;
|
|
case CURLE_SSL_CERTPROBLEM: finish_with = 615; break;
|
|
case CURLE_SSL_CIPHER: finish_with = 616; break;
|
|
case CURLE_SSL_ENGINE_INITFAILED: finish_with = 617; break;
|
|
case CURLE_SSL_CACERT_BADFILE: finish_with = 618; break;
|
|
case CURLE_SSL_CRL_BADFILE: finish_with = 619; break;
|
|
case CURLE_SSL_ISSUER_ERROR: finish_with = 620; break;
|
|
case CURLE_SSL_PINNEDPUBKEYNOTMATCH: finish_with = 621; break;
|
|
case CURLE_SSL_INVALIDCERTSTATUS: finish_with = 609; break;
|
|
|
|
case CURLE_HTTP2:
|
|
case CURLE_HTTP2_STREAM:
|
|
|
|
case CURLE_FAILED_INIT:
|
|
case CURLE_NOT_BUILT_IN:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (finish_with >= 600)
|
|
{
|
|
std::cerr << request->error_buffer << std::endl;
|
|
}
|
|
|
|
request->status = finish_with;
|
|
}
|
|
};
|
|
}
|
|
|
|
std::set<Request *> requests_to_remove;
|
|
for (Request *request : requests)
|
|
{
|
|
bool signal_done = false;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> g(request->rm_mutex);
|
|
if (shutting_down)
|
|
{
|
|
// In the weird case that a http::Request::Simple* call is
|
|
// waiting on this Request, we should fail the request
|
|
// instead of cancelling it ourselves.
|
|
request->status = 610;
|
|
}
|
|
if (!request->rm_canceled && request->rm_started && !request->added_to_multi && !request->status)
|
|
{
|
|
if (multi && request->easy)
|
|
{
|
|
MultiAdd(request);
|
|
}
|
|
else
|
|
{
|
|
request->status = 604;
|
|
}
|
|
}
|
|
if (!request->rm_canceled && request->rm_started && !request->rm_finished)
|
|
{
|
|
if (multi && request->easy)
|
|
{
|
|
#ifdef REQUEST_USE_CURL_OFFSET_T
|
|
curl_easy_getinfo(request->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &request->rm_total);
|
|
curl_easy_getinfo(request->easy, CURLINFO_SIZE_DOWNLOAD_T, &request->rm_done);
|
|
#else
|
|
double total, done;
|
|
curl_easy_getinfo(request->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &total);
|
|
curl_easy_getinfo(request->easy, CURLINFO_SIZE_DOWNLOAD, &done);
|
|
request->rm_total = (curl_off_t)total;
|
|
request->rm_done = (curl_off_t)done;
|
|
#endif
|
|
}
|
|
if (request->status)
|
|
{
|
|
request->rm_finished = true;
|
|
MultiRemove(request);
|
|
signal_done = true;
|
|
}
|
|
}
|
|
if (request->rm_canceled)
|
|
{
|
|
requests_to_remove.insert(request);
|
|
}
|
|
}
|
|
|
|
if (signal_done)
|
|
{
|
|
request->done_cv.notify_one();
|
|
}
|
|
}
|
|
for (Request *request : requests_to_remove)
|
|
{
|
|
requests.erase(request);
|
|
MultiRemove(request);
|
|
delete request;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RequestManager::MultiAdd(Request *request)
|
|
{
|
|
if (multi && request->easy && !request->added_to_multi)
|
|
{
|
|
curl_multi_add_handle(multi, request->easy);
|
|
request->added_to_multi = true;
|
|
++requests_added_to_multi;
|
|
}
|
|
}
|
|
|
|
void RequestManager::MultiRemove(Request *request)
|
|
{
|
|
if (request->added_to_multi)
|
|
{
|
|
curl_multi_remove_handle(multi, request->easy);
|
|
request->added_to_multi = false;
|
|
--requests_added_to_multi;
|
|
}
|
|
}
|
|
|
|
bool RequestManager::AddRequest(Request *request)
|
|
{
|
|
if (!initialized)
|
|
return false;
|
|
{
|
|
std::lock_guard<std::mutex> g(rt_mutex);
|
|
requests_to_add.insert(request);
|
|
}
|
|
rt_cv.notify_one();
|
|
return true;
|
|
}
|
|
|
|
void RequestManager::StartRequest(Request *request)
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> g(rt_mutex);
|
|
requests_to_start = true;
|
|
}
|
|
rt_cv.notify_one();
|
|
}
|
|
|
|
void RequestManager::RemoveRequest(Request *request)
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> g(rt_mutex);
|
|
requests_to_remove = true;
|
|
}
|
|
rt_cv.notify_one();
|
|
}
|
|
}
|
|
#endif
|