Refactor HTTP

Request ownership is no longer flaky. Requests are now owned by the code that makes requests, and Requests and the RequestManager co-own RequestHandles. RequestManager disowns a RequestHandle if it's done with it or if Request code reports that it's no longer needed.

All libcurl code has been moved to RequestManager. This is nice because once NOHTTP is removed, we can add any number of RequestManager implementations, for example one for Android.

Client outliving RequestManager is still a problem, this will have to be addressed later.
This commit is contained in:
Tamás Bálint Misius 2023-01-11 21:14:57 +01:00
parent 29d4d4e91c
commit 91a9973bfd
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
29 changed files with 1004 additions and 981 deletions

View File

@ -39,6 +39,7 @@
#include "client/GameSave.h"
#include "client/SaveFile.h"
#include "client/SaveInfo.h"
#include "client/http/RequestManager.h"
#include "common/Platform.h"
#include "graphics/Graphics.h"
#include "gui/Style.h"
@ -779,10 +780,10 @@ int main(int argc, char * argv[])
ByteString proxyString = clientConfig(arguments["proxy"], "Proxy", "");
ByteString cafileString = clientConfig(arguments["cafile"], "CAFile", "");
ByteString capathString = clientConfig(arguments["capath"], "CAPath", "");
bool disableNetwork = true_arg(arguments["disable-network"]);
auto requestManager = http::RequestManager::Create(proxyString, cafileString, capathString, disableNetwork);
Client::Ref().Initialise(proxyString, cafileString, capathString, disableNetwork);
Client::Ref().Initialize();
// TODO: maybe bind the maximum allowed scale to screen size somehow
if(scale < 1 || scale > SCALE_MAXIMUM)

View File

@ -54,7 +54,6 @@
#include "lua/CommandInterface.h"
#include "client/http/RequestManager.h"
#include "gui/preview/Comment.h"
Client::Client():
@ -102,7 +101,7 @@ Client::Client():
firstRun = true;
}
void Client::Initialise(ByteString proxy, ByteString cafile, ByteString capath, bool disableNetwork)
void Client::Initialize()
{
#if !defined(FONTEDITOR) && !defined(RENDERER)
if (GetPrefBool("version.update", false))
@ -112,11 +111,6 @@ void Client::Initialise(ByteString proxy, ByteString cafile, ByteString capath,
}
#endif
#ifndef NOHTTP
if (!disableNetwork)
http::RequestManager::Ref().Initialise(proxy, cafile, capath);
#endif
//Read stamps library
std::ifstream stampsLib;
stampsLib.open(ByteString::Build(STAMPS_DIR, PATH_SEP, "stamps.def"), std::ios::binary);
@ -132,7 +126,7 @@ void Client::Initialise(ByteString proxy, ByteString cafile, ByteString capath,
stampsLib.close();
//Begin version check
versionCheckRequest = new http::Request(ByteString::Build(SCHEME, SERVER, "/Startup.json"));
versionCheckRequest = std::make_unique<http::Request>(ByteString::Build(SCHEME, SERVER, "/Startup.json"));
if (authUser.UserID)
{
@ -143,7 +137,7 @@ void Client::Initialise(ByteString proxy, ByteString cafile, ByteString capath,
if constexpr (USE_UPDATESERVER)
{
// use an alternate update server
alternateVersionCheckRequest = new http::Request(ByteString::Build(SCHEME, UPDATESERVER, "/Startup.json"));
alternateVersionCheckRequest = std::make_unique<http::Request>(ByteString::Build(SCHEME, UPDATESERVER, "/Startup.json"));
usingAltUpdateServer = true;
if (authUser.UserID)
{
@ -242,25 +236,16 @@ RequestStatus Client::ParseServerReturn(ByteString &result, int status, bool jso
void Client::Tick()
{
if (versionCheckRequest)
{
if (CheckUpdate(versionCheckRequest, true))
versionCheckRequest = nullptr;
}
if (alternateVersionCheckRequest)
{
if (CheckUpdate(alternateVersionCheckRequest, false))
alternateVersionCheckRequest = nullptr;
}
CheckUpdate(versionCheckRequest, true);
CheckUpdate(alternateVersionCheckRequest, false);
}
bool Client::CheckUpdate(http::Request *updateRequest, bool checkSession)
void Client::CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession)
{
//Check status on version check request
if (updateRequest->CheckDone())
if (updateRequest && updateRequest->CheckDone())
{
int status;
ByteString data = updateRequest->Finish(&status);
auto [ status, data ] = updateRequest->Finish();
if (checkSession && status == 618)
{
@ -370,9 +355,8 @@ bool Client::CheckUpdate(http::Request *updateRequest, bool checkSession)
//Do nothing
}
}
return true;
updateRequest.reset();
}
return false;
}
UpdateInfo Client::GetUpdateInfo()
@ -461,19 +445,6 @@ void Client::WritePrefs()
void Client::Shutdown()
{
if (versionCheckRequest)
{
versionCheckRequest->Cancel();
}
if (alternateVersionCheckRequest)
{
alternateVersionCheckRequest->Cancel();
}
#ifndef NOHTTP
http::RequestManager::Ref().Shutdown();
#endif
//Save config
WritePrefs();
}
@ -525,7 +496,7 @@ RequestStatus Client::UploadSave(SaveInfo & save)
return RequestFailure;
}
data = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Save.api"), &dataStatus, userID, authUser.SessionID, {
std::tie(dataStatus, data) = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Save.api"), userID, authUser.SessionID, {
{ "Name", save.GetName().ToUtf8() },
{ "Description", save.GetDescription().ToUtf8() },
{ "Data:save.bin", ByteString(gameData.begin(), gameData.end()) },
@ -700,7 +671,7 @@ RequestStatus Client::ExecVote(int saveID, int direction)
{
ByteString saveIDText = ByteString::Build(saveID);
ByteString userIDText = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Vote.api"), &dataStatus, userIDText, authUser.SessionID, {
std::tie(dataStatus, data) = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Vote.api"), userIDText, authUser.SessionID, {
{ "ID", saveIDText },
{ "Action", direction ? (direction == 1 ? "Up" : "Down") : "Reset" },
{ "Key", authUser.SessionKey }
@ -718,15 +689,13 @@ RequestStatus Client::ExecVote(int saveID, int direction)
std::vector<char> Client::GetSaveData(int saveID, int saveDate)
{
lastError = "";
int dataStatus;
ByteString data;
ByteString urlStr;
if (saveDate)
urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, "_", saveDate, ".cps");
else
urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, ".cps");
data = http::Request::Simple(urlStr, &dataStatus);
auto [ dataStatus, data ] = http::Request::Simple(urlStr);
// will always return failure
ParseServerReturn(data, dataStatus, false);
@ -746,9 +715,7 @@ LoginStatus Client::Login(ByteString username, ByteString password, User & user)
user.SessionID = "";
user.SessionKey = "";
ByteString data;
int dataStatus;
data = http::Request::Simple(ByteString::Build("https://", SERVER, "/Login.json"), &dataStatus, {
auto [ dataStatus, data ] = http::Request::Simple(ByteString::Build("https://", SERVER, "/Login.json"), {
{ "name", username },
{ "pass", password },
});
@ -809,7 +776,7 @@ RequestStatus Client::DeleteSave(int saveID)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{
@ -829,7 +796,7 @@ RequestStatus Client::AddComment(int saveID, String comment)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID, {
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
{ "Comment", comment.ToUtf8() },
{ "Key", authUser.SessionKey }
});
@ -855,7 +822,7 @@ RequestStatus Client::FavouriteSave(int saveID, bool favourite)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(urlStream.Build(), &dataStatus, userID, authUser.SessionID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(urlStream.Build(), userID, authUser.SessionID);
}
else
{
@ -875,7 +842,7 @@ RequestStatus Client::ReportSave(int saveID, String message)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID, {
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
{ "Reason", message.ToUtf8() },
});
}
@ -897,7 +864,7 @@ RequestStatus Client::UnpublishSave(int saveID)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{
@ -917,7 +884,7 @@ RequestStatus Client::PublishSave(int saveID)
if (authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID, {
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
{ "ActionPublish", "bagels" },
});
}
@ -944,12 +911,11 @@ SaveInfo * Client::GetSave(int saveID, int saveDate)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(urlStream.Build(), &dataStatus, userID, authUser.SessionID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(urlStream.Build(), userID, authUser.SessionID);
}
else
{
data = http::Request::Simple(urlStream.Build(), &dataStatus);
std::tie(dataStatus, data) = http::Request::Simple(urlStream.Build());
}
if(dataStatus == 200 && data.size())
{
@ -1047,8 +1013,6 @@ std::vector<std::pair<ByteString, int> > * Client::GetTags(int start, int count,
resultCount = 0;
std::vector<std::pair<ByteString, int> > * tagArray = new std::vector<std::pair<ByteString, int> >();
ByteStringBuilder urlStream;
ByteString data;
int dataStatus;
urlStream << SCHEME << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
if(query.length())
{
@ -1057,7 +1021,7 @@ std::vector<std::pair<ByteString, int> > * Client::GetTags(int start, int count,
urlStream << format::URLEncode(query.ToUtf8());
}
data = http::Request::Simple(urlStream.Build(), &dataStatus);
auto [ dataStatus, data ] = http::Request::Simple(urlStream.Build());
if(dataStatus == 200 && data.size())
{
try
@ -1115,11 +1079,11 @@ std::vector<SaveInfo*> * Client::SearchSaves(int start, int count, String query,
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(urlStream.Build(), &dataStatus, userID, authUser.SessionID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(urlStream.Build(), userID, authUser.SessionID);
}
else
{
data = http::Request::Simple(urlStream.Build(), &dataStatus);
std::tie(dataStatus, data) = http::Request::Simple(urlStream.Build());
}
ParseServerReturn(data, dataStatus, true);
if (dataStatus == 200 && data.size())
@ -1167,7 +1131,7 @@ std::list<ByteString> * Client::RemoveTag(int saveID, ByteString tag)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{
@ -1206,7 +1170,7 @@ std::list<ByteString> * Client::AddTag(int saveID, ByteString tag)
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
data = http::Request::SimpleAuth(url, &dataStatus, userID, authUser.SessionID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{

View File

@ -3,6 +3,7 @@
#include <vector>
#include <list>
#include <memory>
#include "common/String.h"
#include "common/Singleton.h"
@ -51,8 +52,8 @@ private:
String messageOfTheDay;
std::vector<std::pair<String, ByteString> > serverNotifications;
http::Request *versionCheckRequest;
http::Request *alternateVersionCheckRequest;
std::unique_ptr<http::Request> versionCheckRequest;
std::unique_ptr<http::Request> alternateVersionCheckRequest;
bool usingAltUpdateServer;
bool updateAvailable;
UpdateInfo updateInfo;
@ -109,7 +110,7 @@ public:
void SetMessageOfTheDay(String message);
String GetMessageOfTheDay();
void Initialise(ByteString proxy, ByteString cafile, ByteString capath, bool disableNetwork);
void Initialize();
bool IsFirstRun();
void AddListener(ClientListener * listener);
@ -153,7 +154,7 @@ public:
}
RequestStatus ParseServerReturn(ByteString &result, int status, bool json);
void Tick();
bool CheckUpdate(http::Request *updateRequest, bool checkSession);
void CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession);
void Shutdown();
// preferences functions

View File

@ -19,10 +19,8 @@ namespace http
Result result;
try
{
ByteString data = Request::Finish(&result.status);
// Note that at this point it's not safe to use any member of the
// APIRequest object as Request::Finish signals RequestManager
// to delete it.
ByteString data;
std::tie(result.status, data) = Request::Finish();
Client::Ref().ParseServerReturn(data, result.status, true);
if (result.status == 200 && data.size())
{

View File

@ -0,0 +1,15 @@
#pragma once
#include <curl/curl.h> // Has to come first because windows(tm).
#include <stdexcept>
namespace http
{
struct CurlError : public std::runtime_error
{
using runtime_error::runtime_error;
};
void SetupCurlEasyCiphers(CURL *easy);
void HandleCURLcode(CURLcode code);
void HandleCURLMcode(CURLMcode code);
}

View File

@ -18,9 +18,6 @@ namespace http
{
std::unique_ptr<UserInfo> user_info;
auto result = APIRequest::Finish();
// Note that at this point it's not safe to use any member of the
// GetUserInfoRequest object as Request::Finish signals RequestManager
// to delete it.
if (result.document)
{
auto &user = (*result.document)["User"];

View File

@ -23,10 +23,8 @@ namespace http
{
int width = Width;
int height = Height;
ByteString data = Request::Finish(nullptr);
// Note that at this point it's not safe to use any member of the
// ImageRequest object as Request::Finish signals RequestManager
// to delete it.
auto [ status, data ] = Request::Finish();
(void)status; // We don't use this for anything, not ideal >_>
std::unique_ptr<VideoBuffer> vb;
if (data.size())
{

View File

@ -1,139 +1,54 @@
#include "Request.h"
#include "RequestManager.h"
#ifndef NOHTTP
void SetupCurlEasyCiphers(CURL *easy)
{
#ifdef SECURE_CIPHERS_ONLY
curl_version_info_data *version_info = curl_version_info(CURLVERSION_NOW);
ByteString ssl_type = version_info->ssl_version;
if (ssl_type.Contains("OpenSSL"))
{
curl_easy_setopt(easy, CURLOPT_SSL_CIPHER_LIST, "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-GCM-SHA256");
#ifdef REQUEST_USE_CURL_TLSV13CL
curl_easy_setopt(easy, CURLOPT_TLS13_CIPHERS, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256");
#endif
}
else if (ssl_type.Contains("Schannel"))
{
// TODO: add more cipher algorithms
curl_easy_setopt(easy, CURLOPT_SSL_CIPHER_LIST, "CALG_ECDH_EPHEM");
}
#endif
// TODO: Find out what TLS1.2 is supported on, might need to also allow TLS1.0
curl_easy_setopt(easy, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 70, 0)
curl_easy_setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
#elif defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 44, 0)
curl_easy_setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
#elif defined(WIN)
# error "That's unfortunate."
#endif
}
#endif
#include <memory>
namespace http
{
#ifndef NOHTTP
Request::Request(ByteString uri_):
uri(uri_),
rm_total(0),
rm_done(0),
rm_finished(false),
rm_canceled(false),
rm_started(false),
added_to_multi(false),
status(0),
headers(NULL),
#ifdef REQUEST_USE_CURL_MIMEPOST
post_fields(NULL)
#else
post_fields_first(NULL),
post_fields_last(NULL)
#endif
Request::Request(ByteString newUri)
{
easy = curl_easy_init();
if (!RequestManager::Ref().AddRequest(this))
{
status = 604;
rm_finished = true;
}
handle = RequestHandle::Create();
handle->uri = newUri;
}
#else
Request::Request(ByteString uri_) {}
#endif
Request::~Request()
{
#ifndef NOHTTP
curl_easy_cleanup(easy);
#ifdef REQUEST_USE_CURL_MIMEPOST
curl_mime_free(post_fields);
#else
curl_formfree(post_fields_first);
#endif
curl_slist_free_all(headers);
#endif
if (handle->state != RequestHandle::ready)
{
// TODO: Fix bad design.
// Bad design: Client should not outlive RequestManager because it has its own requests, but
// RequestManager needs Client because Client is also responsible for configuration >_>
// Problem: Client outlives RequestManager, RequestManager doesn't necessarily exist at this point.
// Solution: Check if it does >_> ExplicitSingleton::Exists exists for no other reason than this.
if (RequestManager::Exists())
{
RequestManager::Ref().UnregisterRequest(*this);
}
}
}
void Request::Verb(ByteString newVerb)
{
#ifndef NOHTTP
verb = newVerb;
#endif
assert(handle->state == RequestHandle::ready);
handle->verb = newVerb;
}
void Request::AddHeader(ByteString header)
{
#ifndef NOHTTP
headers = curl_slist_append(headers, header.c_str());
#endif
assert(handle->state == RequestHandle::ready);
handle->headers.push_back(header);
}
// add post data to a request
void Request::AddPostData(std::map<ByteString, ByteString> data)
{
#ifndef NOHTTP
assert(handle->state == RequestHandle::ready);
// Even if the map is empty, calling this function signifies you want to do a POST request
isPost = true;
if (!data.size())
{
return;
}
if (easy)
{
#ifdef REQUEST_USE_CURL_MIMEPOST
if (!post_fields)
{
post_fields = curl_mime_init(easy);
}
for (auto &field : data)
{
curl_mimepart *part = curl_mime_addpart(post_fields);
curl_mime_data(part, &field.second[0], field.second.size());
if (auto split = field.first.SplitBy(':'))
{
curl_mime_name(part, split.Before().c_str());
curl_mime_filename(part, split.After().c_str());
}
else
{
curl_mime_name(part, field.first.c_str());
}
}
#else
post_fields_map.insert(data.begin(), data.end());
#endif
}
#endif
handle->isPost = true;
handle->postData.insert(data.begin(), data.end());
}
// add userID and sessionID headers to the request
void Request::AuthHeaders(ByteString ID, ByteString session)
{
assert(handle->state == RequestHandle::ready);
if (ID.size())
{
if (session.size())
@ -148,263 +63,71 @@ namespace http
}
}
#ifndef NOHTTP
size_t Request::HeaderDataHandler(char *ptr, size_t size, size_t count, void *userdata)
{
Request *req = (Request *)userdata;
auto actual_size = size * count;
if (actual_size >= 2 && ptr[actual_size - 2] == '\r' && ptr[actual_size - 1] == '\n')
{
if (actual_size > 2) // don't include header list terminator (but include the status line)
{
req->response_headers.push_back(ByteString(ptr, ptr + actual_size - 2));
}
return actual_size;
}
return 0;
}
size_t Request::WriteDataHandler(char *ptr, size_t size, size_t count, void *userdata)
{
Request *req = (Request *)userdata;
auto actual_size = size * count;
req->response_body.append(ptr, actual_size);
return actual_size;
}
#endif
// start the request thread
void Request::Start()
{
#ifndef NOHTTP
if (CheckStarted() || CheckDone())
assert(handle->state == RequestHandle::ready);
handle->state = RequestHandle::running;
// TODO: Fix bad design.
// Bad design: Client should not outlive RequestManager because it has its own requests, but
// RequestManager needs Client because Client is also responsible for configuration >_>
// Problem: Client outlives RequestManager, RequestManager doesn't necessarily exist at this point.
// Solution: Check if it does >_> ExplicitSingleton::Exists exists for no other reason than this.
if (RequestManager::Exists())
{
return;
RequestManager::Ref().RegisterRequest(*this);
}
if (easy)
{
#ifdef REQUEST_USE_CURL_MIMEPOST
if (post_fields)
{
curl_easy_setopt(easy, CURLOPT_MIMEPOST, post_fields);
}
#else
if (!post_fields_map.empty())
{
for (auto &field : post_fields_map)
{
if (auto split = field.first.SplitBy(':'))
{
curl_formadd(&post_fields_first, &post_fields_last,
CURLFORM_COPYNAME, split.Before().c_str(),
CURLFORM_BUFFER, split.After().c_str(),
CURLFORM_BUFFERPTR, &field.second[0],
CURLFORM_BUFFERLENGTH, field.second.size(),
CURLFORM_END);
}
else
{
curl_formadd(&post_fields_first, &post_fields_last,
CURLFORM_COPYNAME, field.first.c_str(),
CURLFORM_PTRCONTENTS, &field.second[0],
CURLFORM_CONTENTLEN, field.second.size(),
CURLFORM_END);
}
}
curl_easy_setopt(easy, CURLOPT_HTTPPOST, post_fields_first);
}
#endif
else if (isPost)
{
curl_easy_setopt(easy, CURLOPT_POST, 1L);
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, "");
}
else
{
curl_easy_setopt(easy, CURLOPT_HTTPGET, 1L);
}
if (verb.size())
{
curl_easy_setopt(easy, CURLOPT_CUSTOMREQUEST, verb.c_str());
}
curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1L);
if constexpr (ENFORCE_HTTPS)
{
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
curl_easy_setopt(easy, CURLOPT_PROTOCOLS_STR, "https");
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS_STR, "https");
#else
curl_easy_setopt(easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
#endif
}
else
{
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
curl_easy_setopt(easy, CURLOPT_PROTOCOLS_STR, "https,http");
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS_STR, "https,http");
#else
curl_easy_setopt(easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
#endif
}
SetupCurlEasyCiphers(easy);
curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 10L);
curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, error_buffer);
error_buffer[0] = 0;
curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT, timeout);
curl_easy_setopt(easy, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(easy, CURLOPT_URL, uri.c_str());
if (proxy.size())
{
curl_easy_setopt(easy, CURLOPT_PROXY, proxy.c_str());
}
if (cafile.size())
{
curl_easy_setopt(easy, CURLOPT_CAINFO, cafile.c_str());
}
if (capath.size())
{
curl_easy_setopt(easy, CURLOPT_CAPATH, capath.c_str());
}
curl_easy_setopt(easy, CURLOPT_PRIVATE, (void *)this);
curl_easy_setopt(easy, CURLOPT_USERAGENT, user_agent.c_str());
curl_easy_setopt(easy, CURLOPT_HEADERDATA, (void *)this);
curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, Request::HeaderDataHandler);
curl_easy_setopt(easy, CURLOPT_WRITEDATA, (void *)this);
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, Request::WriteDataHandler);
}
{
std::lock_guard<std::mutex> g(rm_mutex);
rm_started = true;
}
RequestManager::Ref().StartRequest(this);
#endif
}
// finish the request (if called before the request is done, this will block)
ByteString Request::Finish(int *status_out, std::vector<ByteString> *headers_out)
bool Request::CheckDone() const
{
#ifndef NOHTTP
if (CheckCanceled())
std::lock_guard lk(handle->stateMx);
assert(handle->state == RequestHandle::running || handle->state == RequestHandle::done);
return handle->state == RequestHandle::done;
}
std::pair<int, int> Request::CheckProgress() const
{
std::lock_guard lk(handle->stateMx);
assert(handle->state == RequestHandle::running || handle->state == RequestHandle::done);
return { handle->bytesTotal, handle->bytesDone };
}
const std::vector<ByteString> &Request::ResponseHeaders() const
{
std::lock_guard lk(handle->stateMx);
assert(handle->state == RequestHandle::done);
return handle->responseHeaders;
}
std::pair<int, ByteString> Request::Finish()
{
std::unique_lock lk(handle->stateMx);
if (handle->state == RequestHandle::running)
{
return ""; // shouldn't happen but just in case
handle->stateCv.wait(lk, [this]() {
return handle->state == RequestHandle::done;
});
}
ByteString response_out;
{
std::unique_lock<std::mutex> l(rm_mutex);
done_cv.wait(l, [this]() { return rm_finished; });
rm_started = false;
rm_canceled = true;
if (status_out)
{
*status_out = status;
}
if (headers_out)
{
*headers_out = std::move(response_headers);
}
response_out = std::move(response_body);
}
RequestManager::Ref().RemoveRequest(this);
return response_out;
#else
if (status_out)
*status_out = 604;
return "";
#endif
assert(handle->state == RequestHandle::done);
handle->state = RequestHandle::dead;
return { handle->statusCode, std::move(handle->responseData) };
}
void Request::CheckProgress(int *total, int *done)
std::pair<int, ByteString> Request::Simple(ByteString uri, std::map<ByteString, ByteString> post_data)
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
if (total)
{
*total = rm_total;
}
if (done)
{
*done = rm_done;
}
#endif
return SimpleAuth(uri, "", "", post_data);
}
// returns true if the request has finished
bool Request::CheckDone()
std::pair<int, ByteString> Request::SimpleAuth(ByteString uri, ByteString ID, ByteString session, std::map<ByteString, ByteString> post_data)
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
return rm_finished;
#else
return true;
#endif
}
// returns true if the request was canceled
bool Request::CheckCanceled()
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
return rm_canceled;
#else
return false;
#endif
}
// returns true if the request is running
bool Request::CheckStarted()
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
return rm_started;
#else
return true;
#endif
}
// cancels the request, the request thread will delete the Request* when it finishes (do not use Request in any way after canceling)
void Request::Cancel()
{
#ifndef NOHTTP
{
std::lock_guard<std::mutex> g(rm_mutex);
rm_canceled = true;
}
RequestManager::Ref().RemoveRequest(this);
#endif
}
ByteString Request::Simple(ByteString uri, int *status, std::map<ByteString, ByteString> post_data)
{
return SimpleAuth(uri, status, "", "", post_data);
}
ByteString Request::SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map<ByteString, ByteString> post_data)
{
Request *request = new Request(uri);
auto request = std::make_unique<Request>(uri);
if (!post_data.empty())
{
request->AddPostData(post_data);
}
request->AuthHeaders(ID, session);
request->Start();
return request->Finish(status);
return request->Finish();
}
String StatusText(int ret)
@ -494,4 +217,3 @@ namespace http
}
}
}

View File

@ -1,68 +1,24 @@
#pragma once
#include "Config.h"
#ifndef NOHTTP
# include <curl/curl.h>
# include "common/tpt-minmax.h" // for MSVC, ensures windows.h doesn't cause compile errors by defining min/max
# include <mutex>
# include <condition_variable>
# if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 55, 0)
# define REQUEST_USE_CURL_OFFSET_T
# endif
# if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 56, 0)
# define REQUEST_USE_CURL_MIMEPOST
# endif
# if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 61, 0)
# define REQUEST_USE_CURL_TLSV13CL
# endif
#endif
#include <map>
#include "common/String.h"
#include <map>
#include <utility>
#include <vector>
#include <mutex>
#include <condition_variable>
namespace http
{
class RequestManager;
struct RequestHandle;
class Request
{
#ifndef NOHTTP
ByteString uri;
std::vector<ByteString> response_headers;
ByteString response_body;
CURL *easy;
char error_buffer[CURL_ERROR_SIZE];
volatile curl_off_t rm_total;
volatile curl_off_t rm_done;
volatile bool rm_finished;
volatile bool rm_canceled;
volatile bool rm_started;
std::mutex rm_mutex;
bool added_to_multi;
int status;
ByteString verb;
struct curl_slist *headers;
bool isPost = false;
#ifdef REQUEST_USE_CURL_MIMEPOST
curl_mime *post_fields;
#else
curl_httppost *post_fields_first, *post_fields_last;
std::map<ByteString, ByteString> post_fields_map;
#endif
std::condition_variable done_cv;
static size_t HeaderDataHandler(char *ptr, size_t size, size_t count, void *userdata);
static size_t WriteDataHandler(char *ptr, size_t size, size_t count, void *userdata);
#endif
std::shared_ptr<RequestHandle> handle;
public:
Request(ByteString uri);
virtual ~Request();
Request(ByteString newUri);
Request(const Request &) = delete;
Request &operator =(const Request &) = delete;
~Request();
void Verb(ByteString newVerb);
void AddHeader(ByteString header);
@ -70,18 +26,16 @@ namespace http
void AuthHeaders(ByteString ID, ByteString session);
void Start();
ByteString Finish(int *status, std::vector<ByteString> *headers = nullptr);
void Cancel();
bool CheckDone() const;
void CheckProgress(int *total, int *done);
bool CheckDone();
bool CheckCanceled();
bool CheckStarted();
std::pair<int, int> CheckProgress() const; // total, done
const std::vector<ByteString> &ResponseHeaders() const;
std::pair<int, ByteString> Finish(); // status, data
static std::pair<int, ByteString> Simple(ByteString uri, std::map<ByteString, ByteString> post_data = {});
static std::pair<int, ByteString> SimpleAuth(ByteString uri, ByteString ID, ByteString session, std::map<ByteString, ByteString> post_data = {});
friend class RequestManager;
static ByteString Simple(ByteString uri, int *status, std::map<ByteString, ByteString> post_data = std::map<ByteString, ByteString>{});
static ByteString SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map<ByteString, ByteString> post_data = std::map<ByteString, ByteString>{});
};
String StatusText(int code);

View File

@ -1,273 +1,114 @@
#ifndef NOHTTP
#include "Request.h" // includes curl.h, needs to come first to silence a warning on windows
#include "RequestManager.h"
#include "Request.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()
RequestManager::RequestManager(ByteString newProxy, ByteString newCafile, ByteString newCapath, bool newDisableNetwork) :
proxy(newProxy),
cafile(newCafile),
capath(newCapath),
disableNetwork(newDisableNetwork)
{
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(
userAgent = ByteString::Build(
"PowderToy/", SAVE_VERSION, ".", MINOR_VERSION,
" (", IDENT_PLATFORM,
"; NO", // Unused, used to be SSE level.
"; M", MOD_ID,
"; ", IDENT,
") TPTPP/", SAVE_VERSION, ".", MINOR_VERSION, ".", BUILD_NUM, ByteString(1, IDENT_RELTYPE), ".", SNAPSHOT_ID
") TPTPP/", SAVE_VERSION, ".", MINOR_VERSION, ".", BUILD_NUM, IDENT_RELTYPE, ".", SNAPSHOT_ID
);
worker = std::thread([this]() {
Worker();
});
}
worker_thread = std::thread([this]() { Worker(); });
initialized = true;
RequestManager::~RequestManager()
{
{
std::lock_guard lk(sharedStateMx);
running = false;
}
worker.join();
}
void RequestManager::Worker()
{
bool shutting_down = false;
while (!shutting_down)
InitWorker();
while (true)
{
{
std::unique_lock<std::mutex> l(rt_mutex);
if (!requests_added_to_multi)
std::lock_guard lk(sharedStateMx);
if (!running)
{
while (!rt_shutting_down && requests_to_add.empty() && !requests_to_start && !requests_to_remove)
// TODO: Fix bad design.
// Bad design: Client should not outlive RequestManager because it has its own requests, but
// RequestManager needs Client because Client is also responsible for configuration >_>
// Problem: RequestManager's worker needs all requests to have been unregistered when it exits.
// Solution: Knock out all live requests here on shutdown.
for (auto &requestHandle : requestHandles)
{
rt_cv.wait(l);
requestHandle->statusCode = 610;
}
}
shutting_down = rt_shutting_down;
requests_to_remove = false;
requests_to_start = false;
for (Request *request : requests_to_add)
for (auto &requestHandle : requestHandles)
{
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)
if (requestHandle->statusCode)
{
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);
requestHandlesToUnregister.push_back(requestHandle);
}
}
if (signal_done)
for (auto &requestHandle : requestHandlesToRegister)
{
request->done_cv.notify_one();
requestHandles.push_back(requestHandle);
RegisterRequestHandle(requestHandle);
}
requestHandlesToRegister.clear();
for (auto &requestHandle : requestHandlesToUnregister)
{
auto eraseFrom = std::remove(requestHandles.begin(), requestHandles.end(), requestHandle);
if (eraseFrom != requestHandles.end())
{
assert(eraseFrom + 1 == requestHandles.end());
UnregisterRequestHandle(requestHandle);
requestHandles.erase(eraseFrom, requestHandles.end());
if (requestHandle->error.size())
{
std::cerr << requestHandle->error << std::endl;
}
{
std::lock_guard lk(requestHandle->stateMx);
requestHandle->state = RequestHandle::done;
}
requestHandle->stateCv.notify_one();
}
}
requestHandlesToUnregister.clear();
if (!running)
{
break;
}
}
for (Request *request : requests_to_remove)
{
requests.erase(request);
MultiRemove(request);
delete request;
}
Tick();
}
assert(!requestHandles.size());
ExitWorker();
}
void RequestManager::MultiAdd(Request *request)
bool RequestManager::DisableNetwork() const
{
if (multi && request->easy && !request->added_to_multi)
{
curl_multi_add_handle(multi, request->easy);
request->added_to_multi = true;
++requests_added_to_multi;
}
return disableNetwork;
}
void RequestManager::MultiRemove(Request *request)
void RequestManager::RegisterRequest(Request &request)
{
if (request->added_to_multi)
{
curl_multi_remove_handle(multi, request->easy);
request->added_to_multi = false;
--requests_added_to_multi;
}
std::lock_guard lk(sharedStateMx);
requestHandlesToRegister.push_back(request.handle);
}
bool RequestManager::AddRequest(Request *request)
void RequestManager::UnregisterRequest(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();
std::lock_guard lk(sharedStateMx);
requestHandlesToUnregister.push_back(request.handle);
}
}
#endif

View File

@ -1,57 +1,101 @@
#pragma once
#include "Config.h"
#ifndef NOHTTP
#include "Config.h"
#include "common/tpt-minmax.h" // for MSVC, ensures windows.h doesn't cause compile errors by defining min/max
#include <thread>
#include <mutex>
#include <condition_variable>
#include <set>
#include <curl/curl.h>
#include "common/Singleton.h"
#include "common/ExplicitSingleton.h"
#include "common/String.h"
#include <thread>
#include <vector>
#include <memory>
#include <mutex>
#include <map>
#include <condition_variable>
namespace http
{
class Request;
class RequestManager : public Singleton<RequestManager>
struct RequestHandle
{
std::thread worker_thread;
std::set<Request *> requests;
int requests_added_to_multi = 0;
std::set<Request *> requests_to_add;
bool requests_to_start = false;
bool requests_to_remove = false;
bool initialized = false;
bool rt_shutting_down = false;
std::mutex rt_mutex;
std::condition_variable rt_cv;
CURLM *multi = nullptr;
void Start();
void Worker();
void MultiAdd(Request *request);
void MultiRemove(Request *request);
bool AddRequest(Request *request);
void StartRequest(Request *request);
void RemoveRequest(Request *request);
protected:
struct CtorTag
{
};
public:
RequestManager() { }
~RequestManager() { }
ByteString uri;
ByteString verb;
bool isPost = false;
std::map<ByteString, ByteString> postData;
std::vector<ByteString> headers;
void Initialise(ByteString newProxy, ByteString newCafile, ByteString newCapath);
void Shutdown();
enum State
{
ready,
running,
done,
dead,
};
State state = ready;
std::mutex stateMx;
std::condition_variable stateCv;
int bytesTotal = 0;
int bytesDone = 0;
int statusCode = 0;
ByteString responseData;
std::vector<ByteString> responseHeaders;
ByteString error;
friend class Request;
RequestHandle(CtorTag)
{
}
RequestHandle(const RequestHandle &) = delete;
RequestHandle &operator =(const RequestHandle &) = delete;
static std::shared_ptr<RequestHandle> Create();
};
extern const long timeout;
extern ByteString proxy;
extern ByteString cafile;
extern ByteString capath;
extern ByteString user_agent;
class RequestManager;
struct RequestManagerDeleter
{
void operator ()(RequestManager *ptr) const;
};
using RequestManagerPtr = std::unique_ptr<RequestManager, RequestManagerDeleter>;
class RequestManager : public ExplicitSingleton<RequestManager>
{
ByteString proxy;
ByteString cafile;
ByteString capath;
ByteString userAgent;
bool disableNetwork;
std::thread worker;
void InitWorker();
void Worker();
void ExitWorker();
std::vector<std::shared_ptr<RequestHandle>> requestHandles;
void RegisterRequestHandle(std::shared_ptr<RequestHandle> requestHandle);
void UnregisterRequestHandle(std::shared_ptr<RequestHandle> requestHandle);
void Tick();
// State shared between Request threads and the worker thread.
std::vector<std::shared_ptr<RequestHandle>> requestHandlesToRegister;
std::vector<std::shared_ptr<RequestHandle>> requestHandlesToUnregister;
bool running = true;
std::mutex sharedStateMx;
protected:
RequestManager(ByteString newProxy, ByteString newCafile, ByteString newCapath, bool newDisableNetwork);
public:
~RequestManager();
void RegisterRequest(Request &request);
void UnregisterRequest(Request &request);
bool DisableNetwork() const;
static RequestManagerPtr Create(ByteString newProxy, ByteString newCafile, ByteString newCapath, bool newDisableNetwork);
};
constexpr int TickMs = 100;
}
#endif

View File

@ -0,0 +1,451 @@
#include <curl/curl.h> // Has to come first because windows(tm).
#include "RequestManager.h"
#include "Request.h"
#include "CurlError.h"
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 55, 0)
# define REQUEST_USE_CURL_OFFSET_T
#endif
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 56, 0)
# define REQUEST_USE_CURL_MIMEPOST
#endif
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 61, 0)
# define REQUEST_USE_CURL_TLSV13CL
#endif
const long curlMaxHostConnections = 1;
const long curlMaxConcurrentStreams = 50;
const long curlConnectTimeoutS = 15;
namespace http
{
void HandleCURLcode(CURLcode code)
{
if (code != CURLE_OK)
{
throw CurlError(curl_easy_strerror(code));
}
};
void HandleCURLMcode(CURLMcode code)
{
if (code != CURLM_OK && code != CURLM_CALL_MULTI_PERFORM)
{
throw CurlError(curl_multi_strerror(code));
}
};
#ifndef REQUEST_USE_CURL_MIMEPOST
void HandleCURLFORMcode(CURLFORMcode code)
{
if (code != CURL_FORMADD_OK)
{
throw CurlError(ByteString::Build("CURLFORMcode ", code));
}
};
#endif
struct RequestHandleHttp : public RequestHandle
{
curl_slist *curlHeaders = NULL;
#ifdef REQUEST_USE_CURL_MIMEPOST
curl_mime *curlPostFields = NULL;
#else
curl_httppost *curlPostFieldsFirst = NULL;
curl_httppost *curlPostFieldsLast = NULL;
#endif
CURL *curlEasy = NULL;
char curlErrorBuffer[CURL_ERROR_SIZE];
bool curlAddedToMulti = false;
RequestHandleHttp() : RequestHandle(CtorTag{})
{
}
static size_t HeaderDataHandler(char *ptr, size_t size, size_t count, void *userdata)
{
auto *handle = (RequestHandleHttp *)userdata;
auto bytes = size * count;
if (bytes >= 2 && ptr[bytes - 2] == '\r' && ptr[bytes - 1] == '\n')
{
if (bytes > 2) // Don't include header list terminator (but include the status line).
{
handle->responseHeaders.push_back(ByteString(ptr, ptr + bytes - 2));
}
return bytes;
}
return 0;
}
static size_t WriteDataHandler(char *ptr, size_t size, size_t count, void *userdata)
{
auto *handle = (RequestHandleHttp *)userdata;
auto bytes = size * count;
handle->responseData.append(ptr, bytes);
return bytes;
}
};
std::shared_ptr<RequestHandle> RequestHandle::Create()
{
return std::make_shared<RequestHandleHttp>();
}
struct RequestManagerHttp : public RequestManager
{
using RequestManager::RequestManager;
bool curlGlobalInit = false;
CURLM *curlMulti = NULL;
};
void RequestManager::InitWorker()
{
auto manager = static_cast<RequestManagerHttp *>(this);
if (!curl_global_init(CURL_GLOBAL_DEFAULT))
{
manager->curlGlobalInit = true;
manager->curlMulti = curl_multi_init();
if (manager->curlMulti)
{
HandleCURLMcode(curl_multi_setopt(manager->curlMulti, CURLMOPT_MAX_HOST_CONNECTIONS, curlMaxHostConnections));
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 67, 0)
HandleCURLMcode(curl_multi_setopt(manager->curlMulti, CURLMOPT_MAX_CONCURRENT_STREAMS, curlMaxConcurrentStreams));
#endif
}
}
}
void RequestManager::ExitWorker()
{
auto manager = static_cast<RequestManagerHttp *>(this);
curl_multi_cleanup(manager->curlMulti);
manager->curlMulti = NULL;
curl_global_cleanup();
}
void RequestManager::RegisterRequestHandle(std::shared_ptr<RequestHandle> requestHandle)
{
auto manager = static_cast<RequestManagerHttp *>(this);
auto handle = static_cast<RequestHandleHttp *>(requestHandle.get());
auto failEarly = [&requestHandle](int statusCode, ByteString error) {
requestHandle->statusCode = statusCode;
requestHandle->error = error;
};
if (disableNetwork)
{
return failEarly(604, "network disabled upon request");
}
if (!manager->curlGlobalInit)
{
return failEarly(600, "no CURL");
}
if (!manager->curlMulti)
{
return failEarly(600, "no CURL multi handle");
}
try
{
handle->curlEasy = curl_easy_init();
if (!handle->curlEasy)
{
return failEarly(600, "no CURL easy handle");
}
for (auto &header : handle->headers)
{
auto *newHeaders = curl_slist_append(handle->curlHeaders, header.c_str());
if (!newHeaders)
{
// Hopefully this is what a NULL from curl_slist_append means.
HandleCURLcode(CURLE_OUT_OF_MEMORY);
}
handle->curlHeaders = newHeaders;
}
{
auto postData = handle->postData;
if (postData.size())
{
#ifdef REQUEST_USE_CURL_MIMEPOST
handle->curlPostFields = curl_mime_init(handle->curlEasy);
if (!handle->curlPostFields)
{
// Hopefully this is what a NULL from curl_mime_init means.
HandleCURLcode(CURLE_OUT_OF_MEMORY);
}
for (auto &field : postData)
{
curl_mimepart *part = curl_mime_addpart(handle->curlPostFields);
if (!part)
{
// Hopefully this is what a NULL from curl_mime_addpart means.
HandleCURLcode(CURLE_OUT_OF_MEMORY);
}
HandleCURLcode(curl_mime_data(part, &field.second[0], field.second.size()));
if (auto split = field.first.SplitBy(':'))
{
HandleCURLcode(curl_mime_name(part, split.Before().c_str()));
HandleCURLcode(curl_mime_filename(part, split.After().c_str()));
}
else
{
HandleCURLcode(curl_mime_name(part, field.first.c_str()));
}
}
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_MIMEPOST, handle->curlPostFields));
#else
for (auto &field : postData)
{
if (auto split = field.first.SplitBy(':'))
{
HandleCURLFORMcode(curl_formadd(&handle->curlPostFieldsFirst, &handle->curlPostFieldsLast,
CURLFORM_COPYNAME, split.Before().c_str(),
CURLFORM_BUFFER, split.After().c_str(),
CURLFORM_BUFFERPTR, &field.second[0],
CURLFORM_BUFFERLENGTH, field.second.size(),
CURLFORM_END));
}
else
{
HandleCURLFORMcode(curl_formadd(&handle->curlPostFieldsFirst, &handle->curlPostFieldsLast,
CURLFORM_COPYNAME, field.first.c_str(),
CURLFORM_PTRCONTENTS, &field.second[0],
CURLFORM_CONTENTLEN, field.second.size(),
CURLFORM_END));
}
}
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_HTTPPOST, handle->curlPostFieldsFirst));
#endif
}
else if (handle->isPost)
{
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_POST, 1L));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_POSTFIELDS, ""));
}
else
{
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_HTTPGET, 1L));
}
if (handle->verb.size())
{
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_CUSTOMREQUEST, handle->verb.c_str()));
}
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_FOLLOWLOCATION, 1L));
if constexpr (ENFORCE_HTTPS)
{
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_PROTOCOLS_STR, "https"));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_REDIR_PROTOCOLS_STR, "https"));
#else
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS));
#endif
}
else
{
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_PROTOCOLS_STR, "https,http"));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_REDIR_PROTOCOLS_STR, "https,http"));
#else
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP));
#endif
}
SetupCurlEasyCiphers(handle->curlEasy);
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_MAXREDIRS, 10L));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_ERRORBUFFER, handle->curlErrorBuffer));
handle->curlErrorBuffer[0] = 0;
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_CONNECTTIMEOUT, curlConnectTimeoutS));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_HTTPHEADER, handle->curlHeaders));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_URL, handle->uri.c_str()));
if (proxy.size())
{
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_PROXY, proxy.c_str()));
}
if (cafile.size())
{
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_CAINFO, cafile.c_str()));
}
if (capath.size())
{
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_CAPATH, capath.c_str()));
}
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_PRIVATE, (void *)handle));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_USERAGENT, userAgent.c_str()));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_HEADERDATA, (void *)handle));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_HEADERFUNCTION, &RequestHandleHttp::HeaderDataHandler));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_WRITEDATA, (void *)handle));
HandleCURLcode(curl_easy_setopt(handle->curlEasy, CURLOPT_WRITEFUNCTION, &RequestHandleHttp::WriteDataHandler));
}
}
catch (const CurlError &ex)
{
return failEarly(600, ex.what());
}
HandleCURLMcode(curl_multi_add_handle(manager->curlMulti, handle->curlEasy));
handle->curlAddedToMulti = true;
}
void RequestManager::UnregisterRequestHandle(std::shared_ptr<RequestHandle> requestHandle)
{
auto manager = static_cast<RequestManagerHttp *>(this);
auto handle = static_cast<RequestHandleHttp *>(requestHandle.get());
if (handle->curlAddedToMulti)
{
HandleCURLMcode(curl_multi_remove_handle(manager->curlMulti, handle->curlEasy));
handle->curlAddedToMulti = false;
}
curl_easy_cleanup(handle->curlEasy);
#ifdef REQUEST_USE_CURL_MIMEPOST
curl_mime_free(handle->curlPostFields);
#else
curl_formfree(handle->curlPostFieldsFirst);
#endif
curl_slist_free_all(handle->curlHeaders);
}
void RequestManager::Tick()
{
if (!requestHandles.size())
{
std::this_thread::sleep_for(std::chrono::milliseconds(TickMs));
return;
}
auto manager = static_cast<RequestManagerHttp *>(this);
int dontcare;
HandleCURLMcode(curl_multi_wait(manager->curlMulti, NULL, 0, TickMs, &dontcare));
HandleCURLMcode(curl_multi_perform(manager->curlMulti, &dontcare));
while (auto msg = curl_multi_info_read(manager->curlMulti, &dontcare))
{
if (msg->msg == CURLMSG_DONE)
{
RequestHandleHttp *handle;
HandleCURLcode(curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &handle));
handle->statusCode = 600;
switch (msg->data.result)
{
case CURLE_OK:
{
long code;
HandleCURLcode(curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code));
assert(code);
handle->statusCode = int(code);
}
break;
case CURLE_UNSUPPORTED_PROTOCOL: handle->statusCode = 601; break;
case CURLE_COULDNT_RESOLVE_HOST: handle->statusCode = 602; break;
case CURLE_OPERATION_TIMEDOUT: handle->statusCode = 605; break;
case CURLE_URL_MALFORMAT: handle->statusCode = 606; break;
case CURLE_COULDNT_CONNECT: handle->statusCode = 607; break;
case CURLE_COULDNT_RESOLVE_PROXY: handle->statusCode = 608; break;
case CURLE_TOO_MANY_REDIRECTS: handle->statusCode = 611; break;
case CURLE_SSL_CONNECT_ERROR: handle->statusCode = 612; break;
case CURLE_SSL_ENGINE_NOTFOUND: handle->statusCode = 613; break;
case CURLE_SSL_ENGINE_SETFAILED: handle->statusCode = 614; break;
case CURLE_SSL_CERTPROBLEM: handle->statusCode = 615; break;
case CURLE_SSL_CIPHER: handle->statusCode = 616; break;
case CURLE_SSL_ENGINE_INITFAILED: handle->statusCode = 617; break;
case CURLE_SSL_CACERT_BADFILE: handle->statusCode = 618; break;
case CURLE_SSL_CRL_BADFILE: handle->statusCode = 619; break;
case CURLE_SSL_ISSUER_ERROR: handle->statusCode = 620; break;
case CURLE_SSL_PINNEDPUBKEYNOTMATCH: handle->statusCode = 621; break;
case CURLE_SSL_INVALIDCERTSTATUS: handle->statusCode = 609; break;
case CURLE_HTTP2:
case CURLE_HTTP2_STREAM:
case CURLE_FAILED_INIT:
case CURLE_NOT_BUILT_IN:
default:
break;
}
if (handle->statusCode >= 600)
{
handle->error = handle->curlErrorBuffer;
}
}
}
for (auto &requestHandle : requestHandles)
{
auto handle = static_cast<RequestHandleHttp *>(requestHandle.get());
if (handle->curlEasy)
{
#ifdef REQUEST_USE_CURL_OFFSET_T
curl_off_t total, done;
HandleCURLcode(curl_easy_getinfo(handle->curlEasy, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &total));
HandleCURLcode(curl_easy_getinfo(handle->curlEasy, CURLINFO_SIZE_DOWNLOAD_T, &done));
#else
double total, done;
HandleCURLcode(curl_easy_getinfo(handle->curlEasy, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &total));
HandleCURLcode(curl_easy_getinfo(handle->curlEasy, CURLINFO_SIZE_DOWNLOAD, &done));
#endif
handle->bytesTotal = int(total);
handle->bytesDone = int(done);
}
else
{
handle->bytesTotal = 0;
handle->bytesDone = 0;
}
}
}
RequestManagerPtr RequestManager::Create(ByteString newProxy, ByteString newCafile, ByteString newCapath, bool newDisableNetwork)
{
return RequestManagerPtr(new RequestManagerHttp(newProxy, newCafile, newCapath, newDisableNetwork));
}
void RequestManagerDeleter::operator ()(RequestManager *ptr) const
{
delete static_cast<RequestManagerHttp *>(ptr);
}
void SetupCurlEasyCiphers(CURL *easy)
{
#ifdef SECURE_CIPHERS_ONLY
curl_version_info_data *version_info = curl_version_info(CURLVERSION_NOW);
ByteString ssl_type = version_info->ssl_version;
if (ssl_type.Contains("OpenSSL"))
{
HandleCURLcode(curl_easy_setopt(easy, CURLOPT_SSL_CIPHER_LIST,
"ECDHE-ECDSA-AES256-GCM-SHA384" ":"
"ECDHE-ECDSA-AES128-GCM-SHA256" ":"
"ECDHE-ECDSA-AES256-SHA384" ":"
"DHE-RSA-AES256-GCM-SHA384" ":"
"ECDHE-RSA-AES256-GCM-SHA384" ":"
"ECDHE-RSA-AES128-GCM-SHA256" ":"
"ECDHE-ECDSA-AES128-SHA" ":"
"ECDHE-ECDSA-AES128-SHA256" ":"
"ECDHE-RSA-CHACHA20-POLY1305" ":"
"ECDHE-RSA-AES256-SHA384" ":"
"ECDHE-RSA-AES128-SHA256" ":"
"ECDHE-ECDSA-CHACHA20-POLY1305" ":"
"ECDHE-ECDSA-AES256-SHA" ":"
"ECDHE-RSA-AES128-SHA" ":"
"DHE-RSA-AES128-GCM-SHA256"
));
#ifdef REQUEST_USE_CURL_TLSV13CL
HandleCURLcode(curl_easy_setopt(easy, CURLOPT_TLS13_CIPHERS,
"TLS_AES_256_GCM_SHA384" ":"
"TLS_CHACHA20_POLY1305_SHA256" ":"
"TLS_AES_128_GCM_SHA256" ":"
"TLS_AES_128_CCM_8_SHA256" ":"
"TLS_AES_128_CCM_SHA256"
));
#endif
}
else if (ssl_type.Contains("Schannel"))
{
// TODO: add more cipher algorithms
HandleCURLcode(curl_easy_setopt(easy, CURLOPT_SSL_CIPHER_LIST, "CALG_ECDH_EPHEM"));
}
#endif
// TODO: Find out what TLS1.2 is supported on, might need to also allow TLS1.0
HandleCURLcode(curl_easy_setopt(easy, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2));
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 70, 0)
HandleCURLcode(curl_easy_setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT));
#elif defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 44, 0)
HandleCURLcode(curl_easy_setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE));
#elif defined(WIN)
# error "That's unfortunate."
#endif
}
}

View File

@ -0,0 +1,43 @@
#include "RequestManager.h"
#include "Request.h"
namespace http
{
std::shared_ptr<RequestHandle> RequestHandle::Create()
{
return std::make_shared<RequestHandle>(CtorTag{});
}
void RequestManager::InitWorker()
{
}
void RequestManager::ExitWorker()
{
}
void RequestManager::RegisterRequestHandle(std::shared_ptr<RequestHandle> requestHandle)
{
requestHandle->statusCode = 604;
requestHandle->error = "network support not compiled in";
}
void RequestManager::UnregisterRequestHandle(std::shared_ptr<RequestHandle> requestHandle)
{
}
void RequestManager::Tick()
{
std::this_thread::sleep_for(std::chrono::milliseconds(TickMs));
}
RequestManagerPtr RequestManager::Create(ByteString newProxy, ByteString newCafile, ByteString newCapath, bool newDisableNetwork)
{
return RequestManagerPtr(new RequestManager(newProxy, newCafile, newCapath, newDisableNetwork));
}
void RequestManagerDeleter::operator ()(RequestManager *ptr) const
{
delete ptr;
}
}

View File

@ -1,50 +0,0 @@
#pragma once
#include <type_traits>
#include <cassert>
namespace http
{
template<class R>
class RequestMonitor
{
R *request;
protected:
RequestMonitor() :
request(nullptr)
{
}
virtual ~RequestMonitor()
{
if (request)
{
request->Cancel();
}
}
void RequestPoll()
{
if (request && request->CheckDone())
{
OnResponse(request->Finish());
request = nullptr;
}
}
template<class... Args>
void RequestSetup(Args&&... args)
{
assert(!request);
request = new R(std::forward<Args>(args)...);
}
void RequestStart()
{
assert(request);
request->Start();
}
virtual void OnResponse(typename std::invoke_result<decltype(&R::Finish), R>::type v) = 0;
};
}

View File

@ -21,9 +21,6 @@ namespace http
bool SaveUserInfoRequest::Finish()
{
auto result = APIRequest::Finish();
// Note that at this point it's not safe to use any member of the
// SaveUserInfoRequest object as Request::Finish signals RequestManager
// to delete it.
if (result.document)
{
return (*result.document)["Status"].asInt() == 1;

View File

@ -5,10 +5,11 @@ client_files += files(
'Request.cpp',
'SaveUserInfoRequest.cpp',
'ThumbnailRequest.cpp',
'RequestManager.cpp',
)
if enable_http
client_files += files(
'RequestManager.cpp',
)
client_files += files('RequestManagerHttp.cpp')
else
client_files += files('RequestManagerNoHttp.cpp')
endif

View File

@ -0,0 +1,36 @@
#pragma once
#include <cassert>
template<class Type>
class ExplicitSingleton
{
static Type *&Instance()
{
// [dcl.fct.spec]: A static local variable in an extern inline function always refers to the same object.
static Type *instance = nullptr;
return instance;
}
public:
ExplicitSingleton()
{
auto &instance = Instance();
assert(!instance);
instance = static_cast<Type *>(this);
}
~ExplicitSingleton()
{
Instance() = nullptr;
}
static Type &Ref()
{
return *Instance();
}
static bool Exists()
{
return Instance();
}
};

View File

@ -17,21 +17,20 @@ AvatarButton::AvatarButton(Point position, Point size, ByteString username):
}
void AvatarButton::OnResponse(std::unique_ptr<VideoBuffer> Avatar)
{
avatar = std::move(Avatar);
}
void AvatarButton::Tick(float dt)
{
if (!avatar && !tried && name.size() > 0)
{
tried = true;
RequestSetup(ByteString::Build(SCHEME, STATICSERVER, "/avatars/", name, ".png"), Size.X, Size.Y);
RequestStart();
imageRequest = std::make_unique<http::ImageRequest>(ByteString::Build(SCHEME, STATICSERVER, "/avatars/", name, ".png"), Size.X, Size.Y);
imageRequest->Start();
}
RequestPoll();
if (imageRequest && imageRequest->CheckDone())
{
avatar = imageRequest->Finish();
imageRequest.reset();
}
}
void AvatarButton::Draw(const Point& screenPos)

View File

@ -5,14 +5,13 @@
#include "graphics/Graphics.h"
#include "gui/interface/Colour.h"
#include "client/http/ImageRequest.h"
#include "client/http/RequestMonitor.h"
#include <memory>
#include <functional>
namespace ui
{
class AvatarButton : public Component, public http::RequestMonitor<http::ImageRequest>
class AvatarButton : public Component
{
std::unique_ptr<VideoBuffer> avatar;
ByteString name;
@ -24,6 +23,8 @@ class AvatarButton : public Component, public http::RequestMonitor<http::ImageRe
};
AvatarButtonAction actionCallback;
std::unique_ptr<http::ImageRequest> imageRequest;
public:
AvatarButton(Point position, Point size, ByteString username);
virtual ~AvatarButton() = default;
@ -39,8 +40,6 @@ public:
void Draw(const Point& screenPos) override;
void Tick(float dt) override;
void OnResponse(std::unique_ptr<VideoBuffer> avatar) override;
void DoAction();
void SetUsername(ByteString username) { name = username; }

View File

@ -118,11 +118,6 @@ SaveButton::~SaveButton()
delete file;
}
void SaveButton::OnResponse(std::unique_ptr<VideoBuffer> Thumbnail)
{
thumbnail = std::move(Thumbnail);
}
void SaveButton::Tick(float dt)
{
if (!thumbnail)
@ -141,8 +136,8 @@ void SaveButton::Tick(float dt)
}
else if (save->GetID())
{
RequestSetup(save->GetID(), save->GetVersion(), thumbBoxSize.X, thumbBoxSize.Y);
RequestStart();
thumbnailRequest = std::make_unique<http::ThumbnailRequest>(save->GetID(), save->GetVersion(), thumbBoxSize.X, thumbBoxSize.Y);
thumbnailRequest->Start();
triedThumbnail = true;
}
}
@ -154,7 +149,11 @@ void SaveButton::Tick(float dt)
}
}
RequestPoll();
if (thumbnailRequest && thumbnailRequest->CheckDone())
{
thumbnail = thumbnailRequest->Finish();
thumbnailRequest.reset();
}
if (thumbnailRenderer)
{

View File

@ -3,7 +3,6 @@
#include "Component.h"
#include "client/http/ThumbnailRequest.h"
#include "client/http/RequestMonitor.h"
#include <memory>
#include <functional>
@ -14,7 +13,7 @@ class SaveInfo;
class ThumbnailRendererTask;
namespace ui
{
class SaveButton : public Component, public http::RequestMonitor<http::ThumbnailRequest>
class SaveButton : public Component
{
SaveFile * file;
SaveInfo * save;
@ -33,6 +32,8 @@ class SaveButton : public Component, public http::RequestMonitor<http::Thumbnail
bool showVotes;
ThumbnailRendererTask *thumbnailRenderer;
std::unique_ptr<http::ThumbnailRequest> thumbnailRequest;
struct SaveButtonAction
{
std::function<void ()> action, altAction, altAltAction, selected;
@ -60,8 +61,6 @@ public:
void Draw(const Point& screenPos) override;
void Tick(float dt) override;
void OnResponse(std::unique_ptr<VideoBuffer> thumbnail) override;
void SetSelected(bool selected_) { selected = selected_; }
bool GetSelected() { return selected; }
void SetSelectable(bool selectable_) { selectable = selectable_; }

View File

@ -23,14 +23,18 @@ PreviewModel::PreviewModel():
saveInfo(NULL),
saveData(NULL),
saveComments(NULL),
saveDataDownload(NULL),
commentsDownload(NULL),
commentBoxEnabled(false),
commentsLoaded(false),
commentsTotal(0),
commentsPageNumber(1)
{
}
PreviewModel::~PreviewModel()
{
delete saveInfo;
delete saveData;
ClearComments();
}
void PreviewModel::SetFavourite(bool favourite)
@ -85,13 +89,13 @@ void PreviewModel::UpdateSave(int saveID, int saveDate)
url = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, "_", saveDate, ".cps");
else
url = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, ".cps");
saveDataDownload = new http::Request(url);
saveDataDownload = std::make_unique<http::Request>(url);
saveDataDownload->Start();
url = ByteString::Build(SCHEME, SERVER , "/Browse/View.json?ID=", saveID);
if (saveDate)
url += ByteString::Build("&Date=", saveDate);
saveInfoDownload = new http::Request(url);
saveInfoDownload = std::make_unique<http::Request>(url);
saveInfoDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
saveInfoDownload->Start();
@ -100,7 +104,7 @@ void PreviewModel::UpdateSave(int saveID, int saveDate)
commentsLoaded = false;
url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", (commentsPageNumber-1)*20, "&Count=20");
commentsDownload = new http::Request(url);
commentsDownload = std::make_unique<http::Request>(url);
commentsDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
commentsDownload->Start();
}
@ -152,7 +156,7 @@ void PreviewModel::UpdateComments(int pageNumber)
if (!GetDoOpen())
{
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", (commentsPageNumber-1)*20, "&Count=20");
commentsDownload = new http::Request(url);
commentsDownload = std::make_unique<http::Request>(url);
commentsDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
commentsDownload->Start();
}
@ -245,11 +249,9 @@ bool PreviewModel::ParseSaveInfo(ByteString &saveInfoResponse)
// Redownload the .cps file for a fixed version of the 404 save
if (tempID == 404 && this->saveID != 404)
{
if (saveDataDownload)
saveDataDownload->Cancel();
delete saveData;
saveData = NULL;
saveDataDownload = new http::Request(ByteString::Build(STATICSCHEME, STATICSERVER, "/2157797.cps"));
saveDataDownload = std::make_unique<http::Request>(ByteString::Build(STATICSCHEME, STATICSERVER, "/2157797.cps"));
saveDataDownload->Start();
}
return true;
@ -293,8 +295,7 @@ void PreviewModel::Update()
{
if (saveDataDownload && saveDataDownload->CheckDone())
{
int status;
ByteString ret = saveDataDownload->Finish(&status);
auto [ status, ret ] = saveDataDownload->Finish();
ByteString nothing;
Client::Ref().ParseServerReturn(nothing, status, true);
@ -312,13 +313,12 @@ void PreviewModel::Update()
observers[i]->SaveLoadingError(Client::Ref().GetLastError());
}
}
saveDataDownload = NULL;
saveDataDownload.reset();
}
if (saveInfoDownload && saveInfoDownload->CheckDone())
{
int status;
ByteString ret = saveInfoDownload->Finish(&status);
auto [ status, ret ] = saveInfoDownload->Finish();
ByteString nothing;
Client::Ref().ParseServerReturn(nothing, status, true);
@ -340,13 +340,12 @@ void PreviewModel::Update()
for (size_t i = 0; i < observers.size(); i++)
observers[i]->SaveLoadingError(Client::Ref().GetLastError());
}
saveInfoDownload = NULL;
saveInfoDownload.reset();
}
if (commentsDownload && commentsDownload->CheckDone())
{
int status;
ByteString ret = commentsDownload->Finish(&status);
auto [ status, ret ] = commentsDownload->Finish();
ClearComments();
ByteString nothing;
@ -358,7 +357,7 @@ void PreviewModel::Update()
notifySaveCommentsChanged();
notifyCommentsPageChanged();
commentsDownload = NULL;
commentsDownload.reset();
}
}
@ -407,17 +406,3 @@ void PreviewModel::AddObserver(PreviewView * observer)
observer->NotifyCommentsPageChanged(this);
observer->NotifyCommentBoxEnabledChanged(this);
}
PreviewModel::~PreviewModel()
{
if (saveDataDownload)
saveDataDownload->Cancel();
if (saveInfoDownload)
saveInfoDownload->Cancel();
if (commentsDownload)
commentsDownload->Cancel();
delete saveInfo;
delete saveData;
ClearComments();
}

View File

@ -2,6 +2,7 @@
#include "Config.h"
#include <vector>
#include <memory>
#include "common/String.h"
namespace http
@ -25,9 +26,9 @@ class PreviewModel
void notifyCommentsPageChanged();
void notifyCommentBoxEnabledChanged();
http::Request * saveDataDownload;
http::Request * saveInfoDownload;
http::Request * commentsDownload;
std::unique_ptr<http::Request> saveDataDownload;
std::unique_ptr<http::Request> saveInfoDownload;
std::unique_ptr<http::Request> commentsDownload;
int saveID;
int saveDate;
@ -38,6 +39,8 @@ class PreviewModel
public:
PreviewModel();
~PreviewModel();
SaveInfo * GetSaveInfo();
std::vector<SaveComment*> * GetComments();
@ -61,5 +64,4 @@ public:
void OnSaveReady();
bool ParseSaveInfo(ByteString &saveInfoResponse);
bool ParseComments(ByteString &commentsResponse);
virtual ~PreviewModel();
};

View File

@ -37,8 +37,8 @@ ProfileActivity::ProfileActivity(ByteString username) :
saving = true;
info.location = location->GetText();
info.biography = bio->GetText();
SaveUserInfoRequestMonitor::RequestSetup(info);
SaveUserInfoRequestMonitor::RequestStart();
saveUserInfoRequest = std::make_unique<http::SaveUserInfoRequest>(info);
saveUserInfoRequest->Start();
}
} });
AddComponent(saveButton);
@ -48,8 +48,8 @@ ProfileActivity::ProfileActivity(ByteString username) :
loading = true;
GetUserInfoRequestMonitor::RequestSetup(username);
GetUserInfoRequestMonitor::RequestStart();
getUserInfoRequest = std::make_unique<http::GetUserInfoRequest>(username);
getUserInfoRequest->Start();
}
void ProfileActivity::setUserInfo(UserInfo newInfo)
@ -191,33 +191,6 @@ void ProfileActivity::setUserInfo(UserInfo newInfo)
scrollPanel->InnerSize = ui::Point(Size.X, currentY);
}
void ProfileActivity::OnResponse(bool SaveUserInfoStatus)
{
if (SaveUserInfoStatus)
{
Exit();
}
else
{
doError = true;
doErrorMessage = "Could not save user info: " + Client::Ref().GetLastError();
}
}
void ProfileActivity::OnResponse(std::unique_ptr<UserInfo> getUserInfoResult)
{
if (getUserInfoResult)
{
loading = false;
setUserInfo(*getUserInfoResult);
}
else
{
doError = true;
doErrorMessage = "Could not load user info: " + Client::Ref().GetLastError();
}
}
void ProfileActivity::OnTick(float dt)
{
if (doError)
@ -226,8 +199,35 @@ void ProfileActivity::OnTick(float dt)
Exit();
}
SaveUserInfoRequestMonitor::RequestPoll();
GetUserInfoRequestMonitor::RequestPoll();
if (saveUserInfoRequest && saveUserInfoRequest->CheckDone())
{
auto SaveUserInfoStatus = saveUserInfoRequest->Finish();
if (SaveUserInfoStatus)
{
Exit();
}
else
{
doError = true;
doErrorMessage = "Could not save user info: " + Client::Ref().GetLastError();
}
saveUserInfoRequest.reset();
}
if (getUserInfoRequest && getUserInfoRequest->CheckDone())
{
auto getUserInfoResult = getUserInfoRequest->Finish();
if (getUserInfoResult)
{
loading = false;
setUserInfo(*getUserInfoResult);
}
else
{
doError = true;
doErrorMessage = "Could not load user info: " + Client::Ref().GetLastError();
}
getUserInfoRequest.reset();
}
}
void ProfileActivity::OnDraw()

View File

@ -4,16 +4,14 @@
#include "client/UserInfo.h"
#include "client/http/SaveUserInfoRequest.h"
#include "client/http/GetUserInfoRequest.h"
#include "client/http/RequestMonitor.h"
#include <memory>
namespace ui
{
class Label;
class ScrollPanel;
}
using SaveUserInfoRequestMonitor = http::RequestMonitor<http::SaveUserInfoRequest>;
using GetUserInfoRequestMonitor = http::RequestMonitor<http::GetUserInfoRequest>;
class ProfileActivity: public WindowActivity, public SaveUserInfoRequestMonitor, public GetUserInfoRequestMonitor {
class ProfileActivity: public WindowActivity {
ui::ScrollPanel *scrollPanel;
ui::Label *location;
ui::Label *bio;
@ -24,6 +22,10 @@ class ProfileActivity: public WindowActivity, public SaveUserInfoRequestMonitor,
bool doError;
String doErrorMessage;
void setUserInfo(UserInfo newInfo);
std::unique_ptr<http::SaveUserInfoRequest> saveUserInfoRequest;
std::unique_ptr<http::GetUserInfoRequest> getUserInfoRequest;
public:
ProfileActivity(ByteString username);
virtual ~ProfileActivity();
@ -31,8 +33,5 @@ public:
void OnDraw() override;
void OnTryExit(ExitMethod method) override;
void OnResponse(bool saveUserInfoStatus) override;
void OnResponse(std::unique_ptr<UserInfo> getUserInfoResult) override;
void ResizeArea();
};

View File

@ -3,6 +3,7 @@
#include "UpdateActivity.h"
#include <bzlib.h>
#include <memory>
#include "Config.h"
#include "Update.h"
@ -32,20 +33,19 @@ private:
bool doWork() override
{
String error;
http::Request *request = new http::Request(updateName);
auto request = std::make_unique<http::Request>(updateName);
request->Start();
notifyStatus("Downloading update");
notifyProgress(-1);
while(!request->CheckDone())
{
int total, done;
request->CheckProgress(&total, &done);
std::tie(total, done) = request->CheckProgress();
notifyProgress(total ? done * 100 / total : 0);
Platform::Millisleep(1);
}
int status;
ByteString data = request->Finish(&status);
auto [ status, data ] = request->Finish();
if (status!=200)
{
error = String::Build("Server responded with Status ", status);

View File

@ -1284,8 +1284,7 @@ int luatpt_getscript(lua_State* l)
if (confirmPrompt && !ConfirmPrompt::Blocking("Do you want to install script?", url.FromUtf8(), "Install"))
return 0;
int ret;
ByteString scriptData = http::Request::Simple(url, &ret);
auto [ ret, scriptData ] = http::Request::Simple(url);
if (!scriptData.size())
{
return luaL_error(l, "Server did not return data");

View File

@ -4233,7 +4233,7 @@ public:
};
private:
http::Request *request;
std::unique_ptr<http::Request> request;
bool dead = false;
RequestType type;
@ -4284,7 +4284,7 @@ public:
}
new(rh) RequestHandle();
rh->type = type;
rh->request = new http::Request(uri);
rh->request = std::make_unique<http::Request>(uri);
if (verb.size())
{
rh->request->Verb(verb);
@ -4322,14 +4322,14 @@ public:
bool Done() const
{
return dead || request->CheckDone();
return request->CheckDone();
}
void Progress(int *total, int *done)
{
if (!dead)
{
request->CheckProgress(total, done);
std::tie(*total, *done) = request->CheckProgress();
}
}
@ -4337,7 +4337,7 @@ public:
{
if (!dead)
{
request->Cancel();
request.reset();
dead = true;
}
}
@ -4349,7 +4349,9 @@ public:
{
if (request->CheckDone())
{
data = request->Finish(&status_out, &headers);
headers = request->ResponseHeaders();
std::tie(status_out, data) = request->Finish();
request.reset();
if (type == getAuthToken && status_out == 200)
{
FinishGetAuthToken(data, status_out, headers);

View File

@ -18,10 +18,10 @@
#endif
#include "LuaScriptInterface.h"
#include "client/http/RequestManager.h"
#include "client/http/CurlError.h"
#include "Misc.h"
void SetupCurlEasyCiphers(CURL *easy);
namespace LuaTCPSocket
{
static double Now()
@ -97,9 +97,10 @@ namespace LuaTCPSocket
static void Reset(TCPSocket *tcps)
{
using http::HandleCURLMcode;
if (tcps->multi)
{
curl_multi_remove_handle(tcps->multi, tcps->easy);
HandleCURLMcode(curl_multi_remove_handle(tcps->multi, tcps->easy));
curl_multi_cleanup(tcps->multi);
tcps->multi = nullptr;
}
@ -113,10 +114,12 @@ namespace LuaTCPSocket
static bool ConnectPerform(TCPSocket *tcps, CURLcode *res)
{
using http::HandleCURLMcode;
while (true)
{
int dontcare;
auto mres = curl_multi_perform(tcps->multi, &dontcare);
http::HandleCURLMcode(mres);
struct CURLMsg *msg;
while ((msg = curl_multi_info_read(tcps->multi, &dontcare)))
{
@ -136,8 +139,13 @@ namespace LuaTCPSocket
static int New(lua_State *l)
{
using http::HandleCURLMcode;
if (http::RequestManager::Ref().DisableNetwork())
{
return luaL_error(l, "network disabled");
}
auto *tcps = (TCPSocket *)lua_newuserdata(l, sizeof(TCPSocket));
new (tcps) TCPSocket;
new(tcps) TCPSocket;
tcps->errorBuf[0] = 0;
tcps->easy = curl_easy_init();
tcps->status = StatusReady;
@ -157,7 +165,7 @@ namespace LuaTCPSocket
Reset(tcps);
return luaL_error(l, "curl_multi_init failed");
}
curl_multi_add_handle(tcps->multi, tcps->easy);
HandleCURLMcode(curl_multi_add_handle(tcps->multi, tcps->easy));
luaL_newmetatable(l, "TCPSocket");
lua_setmetatable(l, -2);
return 1;
@ -440,6 +448,7 @@ namespace LuaTCPSocket
static int Connect(lua_State *l)
{
using http::HandleCURLcode;
auto *tcps = (TCPSocket *)luaL_checkudata(l, 1, "TCPSocket");
if (tcps->status == StatusDead)
{
@ -454,43 +463,51 @@ namespace LuaTCPSocket
{
if (tcps->status != StatusConnecting)
{
tcps->status = StatusConnecting;
// * Using CURLPROTO_HTTPS and CURLPROTO_HTTP with CURL_HTTP_VERSION_1_0
// because these really don't send anything while connecting if
// CURLOPT_CONNECT_ONLY is 1 and there are no proxies involved. The
// only ugly bit is that we have to prepend http:// or https:// to
// the hostnames.
curl_easy_setopt(tcps->easy, CURLOPT_ERRORBUFFER, tcps->errorBuf);
curl_easy_setopt(tcps->easy, CURLOPT_CONNECT_ONLY, 1L);
ByteString address = tpt_lua_checkByteString(l, 2);
curl_easy_setopt(tcps->easy, CURLOPT_PORT, long(luaL_checkinteger(l, 3)));
curl_easy_setopt(tcps->easy, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(tcps->easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
if (lua_toboolean(l, 4))
try
{
tcps->status = StatusConnecting;
// * Using CURLPROTO_HTTPS and CURLPROTO_HTTP with CURL_HTTP_VERSION_1_0
// because these really don't send anything while connecting if
// CURLOPT_CONNECT_ONLY is 1 and there are no proxies involved. The
// only ugly bit is that we have to prepend http:// or https:// to
// the hostnames.
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_ERRORBUFFER, tcps->errorBuf));
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_CONNECT_ONLY, 1L));
ByteString address = tpt_lua_checkByteString(l, 2);
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_PORT, long(luaL_checkinteger(l, 3))));
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_NOSIGNAL, 1L));
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0));
if (lua_toboolean(l, 4))
{
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS_STR, "https");
curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS_STR, "https");
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS_STR, "https"));
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS_STR, "https"));
#else
curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS));
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS));
#endif
SetupCurlEasyCiphers(tcps->easy);
address = "https://" + address;
http::SetupCurlEasyCiphers(tcps->easy);
address = "https://" + address;
}
else
{
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS_STR, "http"));
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS_STR, "http"));
#else
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTP));
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP));
#endif
address = "http://" + address;
}
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_URL, address.c_str()));
}
else
catch (const http::CurlError &ex)
{
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS_STR, "http");
curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS_STR, "http");
#else
curl_easy_setopt(tcps->easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
curl_easy_setopt(tcps->easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP);
#endif
address = "http://" + address;
return luaL_error(l, ex.what());
}
curl_easy_setopt(tcps->easy, CURLOPT_URL, address.c_str());
}
CURLcode res;
if (!ConnectPerform(tcps, &res))
{
@ -542,16 +559,17 @@ namespace LuaTCPSocket
static int GetPeerName(lua_State *l)
{
using http::HandleCURLcode;
auto *tcps = (TCPSocket *)luaL_checkudata(l, 1, "TCPSocket");
if (tcps->status != StatusConnected)
{
return luaL_error(l, "attempt to get remote socket info while not connected");
}
char *address;
curl_easy_getinfo(tcps->easy, CURLINFO_PRIMARY_IP, &address);
HandleCURLcode(curl_easy_getinfo(tcps->easy, CURLINFO_PRIMARY_IP, &address));
lua_pushstring(l, address);
long port;
curl_easy_getinfo(tcps->easy, CURLINFO_PRIMARY_PORT, &port);
HandleCURLcode(curl_easy_getinfo(tcps->easy, CURLINFO_PRIMARY_PORT, &port));
lua_pushinteger(l, port);
return 2;
}
@ -578,38 +596,47 @@ namespace LuaTCPSocket
static int GetSockName(lua_State *l)
{
using http::HandleCURLcode;
auto *tcps = (TCPSocket *)luaL_checkudata(l, 1, "TCPSocket");
if (tcps->status != StatusConnected)
{
return luaL_error(l, "attempt to get local socket info while not connected");
}
char *address;
curl_easy_getinfo(tcps->easy, CURLINFO_LOCAL_IP, &address);
HandleCURLcode(curl_easy_getinfo(tcps->easy, CURLINFO_LOCAL_IP, &address));
lua_pushstring(l, address);
long port;
curl_easy_getinfo(tcps->easy, CURLINFO_LOCAL_PORT, &port);
HandleCURLcode(curl_easy_getinfo(tcps->easy, CURLINFO_LOCAL_PORT, &port));
lua_pushinteger(l, port);
return 2;
}
static int SetOption(lua_State *l)
{
using http::HandleCURLcode;
auto *tcps = (TCPSocket *)luaL_checkudata(l, 1, "TCPSocket");
auto option = tpt_lua_checkByteString(l, 2);
if (byteStringEqualsLiteral(option, "keepalive"))
try
{
curl_easy_setopt(tcps->easy, CURLOPT_TCP_KEEPALIVE, long(lua_toboolean(l, 3)));
return 0;
if (byteStringEqualsLiteral(option, "keepalive"))
{
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_TCP_KEEPALIVE, long(lua_toboolean(l, 3))));
return 0;
}
else if (byteStringEqualsLiteral(option, "tcp-nodelay"))
{
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_TCP_NODELAY, long(lua_toboolean(l, 3))));
return 0;
}
else if (byteStringEqualsLiteral(option, "verify-peer"))
{
HandleCURLcode(curl_easy_setopt(tcps->easy, CURLOPT_SSL_VERIFYPEER, long(lua_toboolean(l, 3))));
return 0;
}
}
else if (byteStringEqualsLiteral(option, "tcp-nodelay"))
catch (const http::CurlError &ex)
{
curl_easy_setopt(tcps->easy, CURLOPT_TCP_NODELAY, long(lua_toboolean(l, 3)));
return 0;
}
else if (byteStringEqualsLiteral(option, "verify-peer"))
{
curl_easy_setopt(tcps->easy, CURLOPT_SSL_VERIFYPEER, long(lua_toboolean(l, 3)));
return 0;
return luaL_error(l, ex.what());
}
return luaL_error(l, "unknown option");
}