Use curl-multi

This commit is contained in:
Learath 2023-12-18 20:01:26 +01:00
parent f298b28026
commit 1dc8496470
21 changed files with 429 additions and 192 deletions

View file

@ -1883,6 +1883,7 @@ set_src(ENGINE_INTERFACE GLOB src/engine
friends.h
ghost.h
graphics.h
http.h
input.h
kernel.h
keys.h

View file

@ -2524,9 +2524,9 @@ int kill_process(PROCESS process);
/**
* Checks if a process is alive.
*
*
* @param process Handle/PID of the process.
*
*
* @return bool Returns true if the process is currently running, false if the process is not running (dead).
*/
bool is_process_alive(PROCESS process);

View file

@ -1447,7 +1447,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy)
m_pMapdownloadTask->Timeout(CTimeout{g_Config.m_ClMapDownloadConnectTimeoutMs, 0, g_Config.m_ClMapDownloadLowSpeedLimit, g_Config.m_ClMapDownloadLowSpeedTime});
m_pMapdownloadTask->MaxResponseSize(1024 * 1024 * 1024); // 1 GiB
m_pMapdownloadTask->ExpectSha256(*pMapSha256);
Engine()->AddJob(m_pMapdownloadTask);
Http()->Run(m_pMapdownloadTask);
}
else
{
@ -2664,6 +2664,7 @@ void CClient::RegisterInterfaces()
#endif
Kernel()->RegisterInterface(static_cast<IFriends *>(&m_Friends), false);
Kernel()->ReregisterInterface(static_cast<IFriends *>(&m_Foes));
Kernel()->RegisterInterface(static_cast<IHttp *>(&m_Http), false);
}
void CClient::InitInterfaces()
@ -2688,12 +2689,12 @@ void CClient::InitInterfaces()
m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage);
m_Http.Init(std::chrono::seconds{1});
m_ServerBrowser.SetBaseInfo(&m_aNetClient[CONN_CONTACT], m_pGameClient->NetVersion());
HttpInit(m_pStorage);
#if defined(CONF_AUTOUPDATE)
m_Updater.Init();
m_Updater.Init(&m_Http);
#endif
m_pConfigManager->RegisterCallback(IFavorites::ConfigSaveCallback, m_pFavorites);
@ -4582,7 +4583,7 @@ void CClient::RequestDDNetInfo()
m_pDDNetInfoTask = HttpGetFile(aUrl, Storage(), m_aDDNetInfoTmp, IStorage::TYPE_SAVE);
m_pDDNetInfoTask->Timeout(CTimeout{10000, 0, 500, 10});
m_pDDNetInfoTask->IpResolve(IPRESOLVE::V4);
Engine()->AddJob(m_pDDNetInfoTask);
Http()->Run(m_pDDNetInfoTask);
}
int CClient::GetPredictionTime()

View file

@ -76,6 +76,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
IStorage *m_pStorage = nullptr;
IEngineTextRender *m_pTextRender = nullptr;
IUpdater *m_pUpdater = nullptr;
CHttp m_Http;
CNetClient m_aNetClient[NUM_CONNS];
CDemoPlayer m_DemoPlayer;
@ -266,6 +267,7 @@ public:
IStorage *Storage() { return m_pStorage; }
IEngineTextRender *TextRender() { return m_pTextRender; }
IUpdater *Updater() { return m_pUpdater; }
IHttp *Http() { return &m_Http; }
CClient();

View file

@ -25,6 +25,7 @@
#include <engine/engine.h>
#include <engine/favorites.h>
#include <engine/friends.h>
#include <engine/http.h>
#include <engine/storage.h>
class CSortWrap
@ -95,6 +96,8 @@ void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVers
m_pFavorites = Kernel()->RequestInterface<IFavorites>();
m_pFriends = Kernel()->RequestInterface<IFriends>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
m_pHttpClient = Kernel()->RequestInterface<IHttp>();
dbg_msg("cserverbrowser", "%p", m_pHttpClient);
m_pPingCache = CreateServerBrowserPingCache(m_pConsole, m_pStorage);
RegisterCommands();
@ -102,7 +105,7 @@ void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVers
void CServerBrowser::OnInit()
{
m_pHttp = CreateServerBrowserHttp(m_pEngine, m_pConsole, m_pStorage, g_Config.m_BrCachedBestServerinfoUrl);
m_pHttp = CreateServerBrowserHttp(m_pEngine, m_pConsole, m_pStorage, m_pHttpClient, g_Config.m_BrCachedBestServerinfoUrl);
}
void CServerBrowser::RegisterCommands()

View file

@ -21,6 +21,7 @@ class IFriends;
class IServerBrowserHttp;
class IServerBrowserPingCache;
class IStorage;
class IHttp;
class CCommunityServer
{
@ -143,6 +144,7 @@ private:
IFriends *m_pFriends = nullptr;
IFavorites *m_pFavorites = nullptr;
IStorage *m_pStorage = nullptr;
IHttp *m_pHttpClient = nullptr;
char m_aNetVersion[128];
bool m_RefreshingHttp = false;

View file

@ -29,7 +29,7 @@ public:
{
MAX_URLS = 16,
};
CChooseMaster(IEngine *pEngine, VALIDATOR pfnValidator, const char **ppUrls, int NumUrls, int PreviousBestIndex);
CChooseMaster(IEngine *pEngine, IHttp *pHttp, VALIDATOR pfnValidator, const char **ppUrls, int NumUrls, int PreviousBestIndex);
virtual ~CChooseMaster();
bool GetBestUrl(const char **pBestUrl) const;
@ -51,26 +51,29 @@ private:
};
class CJob : public IJob
{
CChooseMaster *m_pParent;
CLock m_Lock;
std::shared_ptr<CData> m_pData;
std::unique_ptr<CHttpRequest> m_pHead PT_GUARDED_BY(m_Lock);
std::unique_ptr<CHttpRequest> m_pGet PT_GUARDED_BY(m_Lock);
std::shared_ptr<CHttpRequest> m_pHead PT_GUARDED_BY(m_Lock);
std::shared_ptr<CHttpRequest> m_pGet PT_GUARDED_BY(m_Lock);
void Run() override REQUIRES(!m_Lock);
public:
CJob(std::shared_ptr<CData> pData) :
m_pData(std::move(pData)) {}
CJob(CChooseMaster *pParent, std::shared_ptr<CData> pData) :
m_pParent(pParent), m_pData(std::move(pData)) {}
void Abort() REQUIRES(!m_Lock);
};
IEngine *m_pEngine;
IHttp *m_pHttp;
int m_PreviousBestIndex;
std::shared_ptr<CData> m_pData;
std::shared_ptr<CJob> m_pJob;
};
CChooseMaster::CChooseMaster(IEngine *pEngine, VALIDATOR pfnValidator, const char **ppUrls, int NumUrls, int PreviousBestIndex) :
CChooseMaster::CChooseMaster(IEngine *pEngine, IHttp *pHttp, VALIDATOR pfnValidator, const char **ppUrls, int NumUrls, int PreviousBestIndex) :
m_pEngine(pEngine),
m_pHttp(pHttp),
m_PreviousBestIndex(PreviousBestIndex)
{
dbg_assert(NumUrls >= 0, "no master URLs");
@ -128,7 +131,7 @@ void CChooseMaster::Reset()
void CChooseMaster::Refresh()
{
if(m_pJob == nullptr || m_pJob->Status() == IJob::STATE_DONE)
m_pEngine->AddJob(m_pJob = std::make_shared<CJob>(m_pData));
m_pEngine->AddJob(m_pJob = std::make_shared<CJob>(this, m_pData));
}
void CChooseMaster::CJob::Abort()
@ -171,14 +174,16 @@ void CChooseMaster::CJob::Run()
{
aTimeMs[i] = -1;
const char *pUrl = m_pData->m_aaUrls[aRandomized[i]];
CHttpRequest *pHead = HttpHead(pUrl).release();
std::shared_ptr<CHttpRequest> pHead = std::move(HttpHead(pUrl));
pHead->Timeout(Timeout);
pHead->LogProgress(HTTPLOG::FAILURE);
{
CLockScope ls(m_Lock);
m_pHead = std::unique_ptr<CHttpRequest>(pHead);
m_pHead = pHead;
}
IEngine::RunJobBlocking(pHead);
m_pParent->m_pHttp->Run(pHead);
pHead->Wait();
if(pHead->State() == HTTP_ABORTED)
{
dbg_msg("serverbrowse_http", "master chooser aborted");
@ -188,15 +193,19 @@ void CChooseMaster::CJob::Run()
{
continue;
}
auto StartTime = time_get_nanoseconds();
CHttpRequest *pGet = HttpGet(pUrl).release();
std::shared_ptr<CHttpRequest> pGet = std::move(HttpGet(pUrl));
pGet->Timeout(Timeout);
pGet->LogProgress(HTTPLOG::FAILURE);
{
CLockScope ls(m_Lock);
m_pGet = std::unique_ptr<CHttpRequest>(pGet);
m_pGet = pGet;
}
IEngine::RunJobBlocking(pGet);
m_pParent->m_pHttp->Run(pGet);
pGet->Wait();
auto Time = std::chrono::duration_cast<std::chrono::milliseconds>(time_get_nanoseconds() - StartTime);
if(pHead->State() == HTTP_ABORTED)
{
@ -212,6 +221,7 @@ void CChooseMaster::CJob::Run()
{
continue;
}
bool ParseFailure = m_pData->m_pfnValidator(pJson);
json_value_free(pJson);
if(ParseFailure)
@ -221,6 +231,7 @@ void CChooseMaster::CJob::Run()
dbg_msg("serverbrowse_http", "found master, url='%s' time=%dms", pUrl, (int)Time.count());
aTimeMs[i] = Time.count();
}
// Determine index of the minimum time.
int BestIndex = -1;
int BestTime = 0;
@ -241,6 +252,7 @@ void CChooseMaster::CJob::Run()
dbg_msg("serverbrowse_http", "WARNING: no usable masters found");
return;
}
dbg_msg("serverbrowse_http", "determined best master, url='%s' time=%dms", m_pData->m_aaUrls[BestIndex], BestTime);
m_pData->m_BestIndex.store(BestIndex);
}
@ -248,7 +260,7 @@ void CChooseMaster::CJob::Run()
class CServerBrowserHttp : public IServerBrowserHttp
{
public:
CServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, const char **ppUrls, int NumUrls, int PreviousBestIndex);
CServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IHttp *pHttp, const char **ppUrls, int NumUrls, int PreviousBestIndex);
~CServerBrowserHttp() override;
void Update() override;
bool IsRefreshing() override { return m_State != STATE_DONE; }
@ -286,6 +298,7 @@ private:
IEngine *m_pEngine;
IConsole *m_pConsole;
IHttp *m_pHttp;
int m_State = STATE_DONE;
std::shared_ptr<CHttpRequest> m_pGetServers;
@ -295,10 +308,11 @@ private:
std::vector<NETADDR> m_vLegacyServers;
};
CServerBrowserHttp::CServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, const char **ppUrls, int NumUrls, int PreviousBestIndex) :
CServerBrowserHttp::CServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IHttp *pHttp, const char **ppUrls, int NumUrls, int PreviousBestIndex) :
m_pEngine(pEngine),
m_pConsole(pConsole),
m_pChooseMaster(new CChooseMaster(pEngine, Validate, ppUrls, NumUrls, PreviousBestIndex))
m_pHttp(pHttp),
m_pChooseMaster(new CChooseMaster(pEngine, pHttp, Validate, ppUrls, NumUrls, PreviousBestIndex))
{
m_pChooseMaster->Refresh();
}
@ -328,7 +342,7 @@ void CServerBrowserHttp::Update()
m_pGetServers = HttpGet(pBestUrl);
// 10 seconds connection timeout, lower than 8KB/s for 10 seconds to fail.
m_pGetServers->Timeout(CTimeout{10000, 0, 8000, 10});
m_pEngine->AddJob(m_pGetServers);
m_pHttp->Run(m_pGetServers);
m_State = STATE_REFRESHING;
}
else if(m_State == STATE_REFRESHING)
@ -469,7 +483,7 @@ static const char *DEFAULT_SERVERLIST_URLS[] = {
"https://master4.ddnet.org/ddnet/15/servers.json",
};
IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IStorage *pStorage, const char *pPreviousBestUrl)
IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IStorage *pStorage, IHttp *pHttp, const char *pPreviousBestUrl)
{
char aaUrls[CChooseMaster::MAX_URLS][256];
const char *apUrls[CChooseMaster::MAX_URLS] = {0};
@ -506,5 +520,5 @@ IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole
break;
}
}
return new CServerBrowserHttp(pEngine, pConsole, ppUrls, NumUrls, PreviousBestIndex);
return new CServerBrowserHttp(pEngine, pConsole, pHttp, ppUrls, NumUrls, PreviousBestIndex);
}

View file

@ -6,6 +6,7 @@ class CServerInfo;
class IConsole;
class IEngine;
class IStorage;
class IHttp;
class IServerBrowserHttp
{
@ -25,5 +26,5 @@ public:
virtual const NETADDR &LegacyServer(int Index) const = 0;
};
IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IStorage *pStorage, const char *pPreviousBestUrl);
IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IStorage *pStorage, IHttp *pHttp, const char *pPreviousBestUrl);
#endif // ENGINE_CLIENT_SERVERBROWSER_HTTP_H

View file

@ -25,7 +25,7 @@ class CUpdaterFetchTask : public CHttpRequest
void OnProgress() override;
protected:
int OnCompletion(int State) override;
void OnCompletion() override;
public:
CUpdaterFetchTask(CUpdater *pUpdater, const char *pFile, const char *pDestPath);
@ -61,10 +61,8 @@ void CUpdaterFetchTask::OnProgress()
m_pUpdater->m_Percent = Progress();
}
int CUpdaterFetchTask::OnCompletion(int State)
void CUpdaterFetchTask::OnCompletion()
{
State = CHttpRequest::OnCompletion(State);
const char *pFileName = 0;
for(const char *pPath = Dest(); *pPath; pPath++)
if(*pPath == '/')
@ -72,27 +70,26 @@ int CUpdaterFetchTask::OnCompletion(int State)
pFileName = pFileName ? pFileName : Dest();
if(!str_comp(pFileName, "update.json"))
{
if(State == HTTP_DONE)
if(State() == HTTP_DONE)
m_pUpdater->SetCurrentState(IUpdater::GOT_MANIFEST);
else if(State == HTTP_ERROR)
else if(State() == HTTP_ERROR)
m_pUpdater->SetCurrentState(IUpdater::FAIL);
}
else if(!str_comp(pFileName, m_pUpdater->m_aLastFile))
{
if(State == HTTP_DONE)
if(State() == HTTP_DONE)
m_pUpdater->SetCurrentState(IUpdater::MOVE_FILES);
else if(State == HTTP_ERROR)
else if(State() == HTTP_ERROR)
m_pUpdater->SetCurrentState(IUpdater::FAIL);
}
return State;
}
CUpdater::CUpdater()
{
m_pClient = NULL;
m_pStorage = NULL;
m_pEngine = NULL;
m_pClient = nullptr;
m_pStorage = nullptr;
m_pEngine = nullptr;
m_pHttp = nullptr;
m_State = CLEAN;
m_Percent = 0;
@ -100,11 +97,12 @@ CUpdater::CUpdater()
IStorage::FormatTmpPath(m_aServerExecTmp, sizeof(m_aServerExecTmp), SERVER_EXEC);
}
void CUpdater::Init()
void CUpdater::Init(CHttp *pHttp)
{
m_pClient = Kernel()->RequestInterface<IClient>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
m_pEngine = Kernel()->RequestInterface<IEngine>();
m_pHttp = pHttp;
}
void CUpdater::SetCurrentState(int NewState)
@ -133,7 +131,7 @@ int CUpdater::GetCurrentPercent()
void CUpdater::FetchFile(const char *pFile, const char *pDestPath)
{
m_pEngine->AddJob(std::make_shared<CUpdaterFetchTask>(this, pFile, pDestPath));
m_pHttp->Run(std::make_shared<CUpdaterFetchTask>(this, pFile, pDestPath));
}
bool CUpdater::MoveFile(const char *pFile)

View file

@ -41,6 +41,7 @@ class CUpdater : public IUpdater
class IClient *m_pClient;
class IStorage *m_pStorage;
class IEngine *m_pEngine;
class CHttp *m_pHttp;
CLock m_Lock;
@ -77,7 +78,7 @@ public:
int GetCurrentPercent() override REQUIRES(!m_Lock);
void InitiateUpdate() override;
void Init();
void Init(CHttp *pHttp);
void Update() override;
};

16
src/engine/http.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef HTTP_H
#define HTTP_H
#include "kernel.h"
class IHttpRequest {};
class IHttp : public IInterface
{
MACRO_INTERFACE("http", 0)
public:
virtual void Run(std::shared_ptr<IHttpRequest> pRequest) = 0;
};
#endif

View file

@ -8,6 +8,9 @@
#include <engine/storage.h>
#include <game/version.h>
#include <thread>
#include <limits>
#if !defined(CONF_FAMILY_WINDOWS)
#include <csignal>
#endif
@ -15,35 +18,6 @@
#define WIN32_LEAN_AND_MEAN
#include <curl/curl.h>
// TODO: Non-global pls?
static CURLSH *gs_pShare;
static CLock gs_aLocks[CURL_LOCK_DATA_LAST + 1];
static bool gs_Initialized = false;
static int GetLockIndex(int Data)
{
if(!(0 <= Data && Data < CURL_LOCK_DATA_LAST))
{
Data = CURL_LOCK_DATA_LAST;
}
return Data;
}
static void CurlLock(CURL *pHandle, curl_lock_data Data, curl_lock_access Access, void *pUser) ACQUIRE(gs_aLocks[GetLockIndex(Data)])
{
(void)pHandle;
(void)Access;
(void)pUser;
gs_aLocks[GetLockIndex(Data)].lock();
}
static void CurlUnlock(CURL *pHandle, curl_lock_data Data, void *pUser) RELEASE(gs_aLocks[GetLockIndex(Data)])
{
(void)pHandle;
(void)pUser;
gs_aLocks[GetLockIndex(Data)].unlock();
}
int CurlDebug(CURL *pHandle, curl_infotype Type, char *pData, size_t DataSize, void *pUser)
{
char TypeChar;
@ -71,39 +45,6 @@ int CurlDebug(CURL *pHandle, curl_infotype Type, char *pData, size_t DataSize, v
return 0;
}
bool HttpInit(IStorage *pStorage)
{
if(curl_global_init(CURL_GLOBAL_DEFAULT))
{
return true;
}
gs_pShare = curl_share_init();
if(!gs_pShare)
{
return true;
}
// print curl version
{
curl_version_info_data *pVersion = curl_version_info(CURLVERSION_NOW);
dbg_msg("http", "libcurl version %s (compiled = " LIBCURL_VERSION ")", pVersion->version);
}
curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt(gs_pShare, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
curl_share_setopt(gs_pShare, CURLSHOPT_LOCKFUNC, CurlLock);
curl_share_setopt(gs_pShare, CURLSHOPT_UNLOCKFUNC, CurlUnlock);
#if !defined(CONF_FAMILY_WINDOWS)
// As a multithreaded application we have to tell curl to not install signal
// handlers and instead ignore SIGPIPE from OpenSSL ourselves.
signal(SIGPIPE, SIG_IGN);
#endif
gs_Initialized = true;
return false;
}
void EscapeUrl(char *pBuf, int Size, const char *pStr)
{
char *pEsc = curl_easy_escape(0, pStr, 0);
@ -143,24 +84,6 @@ CHttpRequest::~CHttpRequest()
}
}
void CHttpRequest::Run()
{
dbg_assert(gs_Initialized, "must initialize HTTP before running HTTP requests");
int FinalState;
if(!BeforeInit())
{
FinalState = HTTP_ERROR;
}
else
{
CURL *pHandle = curl_easy_init();
FinalState = RunImpl(pHandle);
curl_easy_cleanup(pHandle);
}
m_State = OnCompletion(FinalState);
}
bool CHttpRequest::BeforeInit()
{
if(m_WriteToFile)
@ -181,12 +104,12 @@ bool CHttpRequest::BeforeInit()
return true;
}
int CHttpRequest::RunImpl(CURL *pUser)
bool CHttpRequest::ConfigureHandle(void *pUser)
{
CURL *pHandle = (CURL *)pUser;
if(!pHandle)
if(!BeforeInit())
{
return HTTP_ERROR;
return false;
}
if(g_Config.m_DbgCurl)
@ -199,8 +122,8 @@ int CHttpRequest::RunImpl(CURL *pUser)
{
Protocols |= CURLPROTO_HTTP;
}
char aErr[CURL_ERROR_SIZE];
curl_easy_setopt(pHandle, CURLOPT_ERRORBUFFER, aErr);
curl_easy_setopt(pHandle, CURLOPT_ERRORBUFFER, m_aErr);
curl_easy_setopt(pHandle, CURLOPT_CONNECTTIMEOUT_MS, m_Timeout.ConnectTimeoutMs);
curl_easy_setopt(pHandle, CURLOPT_TIMEOUT_MS, m_Timeout.TimeoutMs);
@ -211,7 +134,6 @@ int CHttpRequest::RunImpl(CURL *pUser)
curl_easy_setopt(pHandle, CURLOPT_MAXFILESIZE_LARGE, (curl_off_t)m_MaxResponseSize);
}
curl_easy_setopt(pHandle, CURLOPT_SHARE, gs_pShare);
// CURLOPT_PROTOCOLS is deprecated: since 7.85.0. Use CURLOPT_PROTOCOLS_STR
// Wait until all platforms have 7.85.0
#ifdef __GNUC__
@ -285,22 +207,7 @@ int CHttpRequest::RunImpl(CURL *pUser)
curl_easy_setopt(pHandle, CURLOPT_HTTPHEADER, m_pHeaders);
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL)
dbg_msg("http", "fetching %s", m_aUrl);
m_State = HTTP_RUNNING;
int Ret = curl_easy_perform(pHandle);
if(Ret != CURLE_OK)
{
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE)
dbg_msg("http", "%s failed. libcurl error (%d): %s", m_aUrl, (int)Ret, aErr);
return (Ret == CURLE_ABORTED_BY_CALLBACK) ? HTTP_ABORTED : HTTP_ERROR;
}
else
{
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL)
dbg_msg("http", "task done %s", m_aUrl);
return HTTP_DONE;
}
return true;
}
size_t CHttpRequest::OnData(char *pData, size_t DataSize)
@ -356,10 +263,29 @@ int CHttpRequest::ProgressCallback(void *pUser, double DlTotal, double DlCurr, d
return pTask->m_Abort ? -1 : 0;
}
int CHttpRequest::OnCompletion(int State)
void CHttpRequest::OnCompletionInternal(std::optional<unsigned int> Result)
{
if(m_Abort)
State = HTTP_ABORTED;
int State;
if(Result.has_value())
{
CURLcode Code = static_cast<CURLcode>(Result.value());
if(Code != CURLE_OK)
{
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE)
dbg_msg("http", "%s failed. libcurl error (%u): %s", m_aUrl, Code, m_aErr);
State = (Code == CURLE_ABORTED_BY_CALLBACK) ? HTTP_ABORTED : HTTP_ERROR;
}
else
{
if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL)
dbg_msg("http", "task done: %s", m_aUrl);
State = HTTP_DONE;
}
}
else {
dbg_msg("http", "%s failed. internal error: %s", m_aUrl, m_aErr);
State = HTTP_ERROR;
}
if(State == HTTP_DONE && m_ExpectedSha256 != SHA256_ZEROED)
{
@ -391,7 +317,9 @@ int CHttpRequest::OnCompletion(int State)
fs_remove(m_aDestAbsolute);
}
}
return State;
m_State = State;
OnCompletion();
}
void CHttpRequest::WriteToFile(IStorage *pStorage, const char *pDest, int StorageType)
@ -413,6 +341,20 @@ void CHttpRequest::Header(const char *pNameColonValue)
m_pHeaders = curl_slist_append((curl_slist *)m_pHeaders, pNameColonValue);
}
void CHttpRequest::Wait()
{
using namespace std::chrono_literals;
// This is so uncommon that polling just might work
for(;;) {
int State = m_State.load(std::memory_order_seq_cst);
if(State != HTTP_QUEUED && State != HTTP_RUNNING) {
return;
}
std::this_thread::sleep_for(10ms);
}
}
void CHttpRequest::Result(unsigned char **ppResult, size_t *pResultLength) const
{
if(m_WriteToFile || State() != HTTP_DONE)
@ -436,3 +378,192 @@ json_value *CHttpRequest::ResultJson() const
}
return json_parse((char *)pResult, ResultLength);
}
bool CHttp::Init(std::chrono::milliseconds ShutdownDelay)
{
m_ShutdownDelay = ShutdownDelay;
#if !defined(CONF_FAMILY_WINDOWS)
// As a multithreaded application we have to tell curl to not install signal
// handlers and instead ignore SIGPIPE from OpenSSL ourselves.
signal(SIGPIPE, SIG_IGN);
#endif
m_pThread = thread_init(CHttp::ThreadMain, this, "http");
std::unique_lock Lock(m_Lock);
m_Cv.wait(Lock, [this](){ return m_State != UNINITIALIZED; });
if(m_State != RUNNING)
{
return false;
}
return true;
}
void CHttp::ThreadMain(void *pUser)
{
CHttp *pHttp = static_cast<CHttp *>(pUser);
pHttp->RunLoop();
}
void CHttp::RunLoop()
{
std::unique_lock Lock(m_Lock);
if(curl_global_init(CURL_GLOBAL_DEFAULT))
{
dbg_msg("http", "curl_global_init failed");
m_State = ERROR;
m_Cv.notify_all();
return;
}
m_pMultiH = curl_multi_init();
if(!m_pMultiH)
{
dbg_msg("http", "curl_multi_init failed");
m_State = ERROR;
m_Cv.notify_all();
return;
}
// print curl version
{
curl_version_info_data *pVersion = curl_version_info(CURLVERSION_NOW);
dbg_msg("http", "libcurl version %s (compiled = " LIBCURL_VERSION ")", pVersion->version);
}
m_State = RUNNING;
m_Cv.notify_all();
dbg_msg("http", "running");
Lock.unlock();
while(m_State == RUNNING) {
static int NextTimeout = std::numeric_limits<int>::max();
int Events = 0;
CURLMcode mc = curl_multi_poll(m_pMultiH, NULL, 0, NextTimeout, &Events);
// We may have been woken up for a shutdown
if(m_Shutdown) {
auto Now = std::chrono::steady_clock::now();
if(!m_ShutdownTime.has_value()) {
m_ShutdownTime = Now + m_ShutdownDelay;
NextTimeout = m_ShutdownDelay.count();
}
else if(m_ShutdownTime < Now || m_RunningRequests.empty()) {
break;
}
}
if(mc != CURLM_OK) {
Lock.lock();
dbg_msg("http", "Failed multi wait: %s", curl_multi_strerror(mc));
m_State = ERROR;
break;
}
mc = curl_multi_perform(m_pMultiH, &Events);
if(mc != CURLM_OK) {
Lock.lock();
dbg_msg("http", "Failed multi perform: %s", curl_multi_strerror(mc));
m_State = ERROR;
break;
}
struct CURLMsg *m;
while((m = curl_multi_info_read(m_pMultiH, &Events))) {
if(m->msg == CURLMSG_DONE) {
auto RequestIt = m_RunningRequests.find(m->easy_handle);
dbg_assert(RequestIt != m_RunningRequests.end(), "Running handle not added to map");
auto pRequest = std::move(RequestIt->second);
m_RunningRequests.erase(RequestIt);
pRequest->OnCompletionInternal(m->data.result);
curl_multi_remove_handle(m_pMultiH, m->easy_handle);
curl_easy_cleanup(m->easy_handle);
}
}
decltype(m_PendingRequests) NewRequests = {};
Lock.lock();
std::swap(m_PendingRequests, NewRequests);
Lock.unlock();
while(!NewRequests.empty()) {
auto &pRequest = NewRequests.front();
dbg_msg("http", "task: %s %s", CHttpRequest::GetRequestType(pRequest->m_Type), pRequest->m_aUrl);
CURL *pEH = curl_easy_init();
if(!pEH)
goto error_init;
if(!pRequest->ConfigureHandle(pEH))
goto error_configure;
mc = curl_multi_add_handle(m_pMultiH, pEH);
if(mc != CURLM_OK)
goto error_configure;
m_RunningRequests.emplace(pEH, std::move(pRequest));
NewRequests.pop_front();
continue;
error_configure:
curl_easy_cleanup(pEH);
error_init:
dbg_msg("http", "failed to start new request");
Lock.lock();
m_State = ERROR;
break;
}
// Only happens if m_State == ERROR, thus we already hold the lock
if(!NewRequests.empty()) {
m_PendingRequests.insert(m_PendingRequests.end(), std::make_move_iterator(NewRequests.begin()), std::make_move_iterator(NewRequests.end()));
break;
}
}
if(!Lock.owns_lock())
Lock.lock();
bool Cleanup = m_State != ERROR;
for(auto &pRequest : m_PendingRequests) {
str_copy(pRequest->m_aErr, "Shutting down");
pRequest->OnCompletionInternal(std::nullopt);
}
for(auto &ReqPair : m_RunningRequests) {
auto &[pHandle, pRequest] = ReqPair;
if(Cleanup) {
curl_multi_remove_handle(m_pMultiH, pHandle);
curl_easy_cleanup(pHandle);
}
str_copy(pRequest->m_aErr, "Shutting down");
pRequest->OnCompletionInternal(std::nullopt);
}
if(Cleanup) {
curl_multi_cleanup(m_pMultiH);
curl_global_cleanup();
}
}
void CHttp::Run(std::shared_ptr<IHttpRequest> pRequest)
{
std::unique_lock Lock(m_Lock);
m_Cv.wait(Lock, [this](){ return m_State != UNINITIALIZED; });
m_PendingRequests.emplace_back(std::move(std::static_pointer_cast<CHttpRequest>(pRequest)));
curl_multi_wakeup(m_pMultiH);
}
void CHttp::Shutdown()
{
std::unique_lock Lock(m_Lock);
if(m_Shutdown || m_State != RUNNING)
return;
m_Shutdown = true;
curl_multi_wakeup(m_pMultiH);
}

View file

@ -7,6 +7,12 @@
#include <algorithm>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <deque>
#include <optional>
#include <engine/http.h>
typedef struct _json_value json_value;
class IStorage;
@ -42,8 +48,10 @@ struct CTimeout
long LowSpeedTime;
};
class CHttpRequest : public IJob
class CHttpRequest : public IHttpRequest
{
friend class CHttp;
enum class REQUEST
{
GET = 0,
@ -51,6 +59,24 @@ class CHttpRequest : public IJob
POST,
POST_JSON,
};
static constexpr const char *GetRequestType(REQUEST Type)
{
switch(Type)
{
case REQUEST::GET:
return "GET";
case REQUEST::HEAD:
return "HEAD";
case REQUEST::POST:
case REQUEST::POST_JSON:
return "POST";
}
// Unreachable, maybe assert instead?
return "UNKNOWN";
}
char m_aUrl[256] = {0};
void *m_pHeaders = nullptr;
@ -83,13 +109,14 @@ class CHttpRequest : public IJob
HTTPLOG m_LogProgress = HTTPLOG::ALL;
IPRESOLVE m_IpResolve = IPRESOLVE::WHATEVER;
char m_aErr[256]; // 256 == CURL_ERROR_SIZE
std::atomic<int> m_State{HTTP_QUEUED};
std::atomic<bool> m_Abort{false};
void Run() override;
// Abort the request with an error if `BeforeInit()` returns false.
bool BeforeInit();
int RunImpl(void *pUser);
bool ConfigureHandle(void *pHandle); // void * == CURL *
void OnCompletionInternal(std::optional<unsigned int> Result); // unsigned int == CURLcode
// Abort the request if `OnData()` returns something other than
// `DataSize`.
@ -99,8 +126,9 @@ class CHttpRequest : public IJob
static size_t WriteCallback(char *pData, size_t Size, size_t Number, void *pUser);
protected:
virtual void OnProgress() {}
virtual int OnCompletion(int State);
// These run on the curl thread now, DO NOT STALL THE THREAD
virtual void OnProgress() {};
virtual void OnCompletion() {};
public:
CHttpRequest(const char *pUrl);
@ -157,8 +185,11 @@ public:
double Size() const { return m_Size.load(std::memory_order_relaxed); }
int Progress() const { return m_Progress.load(std::memory_order_relaxed); }
int State() const { return m_State; }
bool Done() const { int State = m_State; return State != HTTP_QUEUED && State != HTTP_DONE; }
void Abort() { m_Abort = true; }
void Wait();
void Result(unsigned char **ppResult, size_t *pResultLength) const;
json_value *ResultJson() const;
};
@ -202,4 +233,41 @@ inline std::unique_ptr<CHttpRequest> HttpPostJson(const char *pUrl, const char *
bool HttpInit(IStorage *pStorage);
void EscapeUrl(char *pBuf, int Size, const char *pStr);
bool HttpHasIpresolveBug();
// In an ideal world this would be a kernel interface
class CHttp : public IHttp
{
enum EState {
UNINITIALIZED,
RUNNING,
STOPPING,
ERROR,
};
void *m_pThread = nullptr;
std::mutex m_Lock{};
std::condition_variable m_Cv{};
std::atomic<EState> m_State = UNINITIALIZED;
std::deque<std::shared_ptr<CHttpRequest>> m_PendingRequests{};
std::unordered_map<void *, std::shared_ptr<CHttpRequest>> m_RunningRequests{}; // void * == CURL *
std::chrono::milliseconds m_ShutdownDelay{};
std::optional<std::chrono::time_point<std::chrono::steady_clock>> m_ShutdownTime{};
std::atomic<bool> m_Shutdown = false;
// Only to be used with curl_multi_wakeup
void *m_pMultiH = nullptr; // void * == CURLM *
static void ThreadMain(void *pUser);
void RunLoop();
public:
// Startup
bool Init(std::chrono::milliseconds ShutdownDelay);
// User
virtual void Run(std::shared_ptr<IHttpRequest> pRequest) override;
void Shutdown() override;
};
#endif // ENGINE_SHARED_HTTP_H

View file

@ -40,3 +40,5 @@ class IClient *CComponent::Client() const
{
return m_pClient->Client();
}
class IHttp *CComponent::Http() const { return m_pClient->Http(); }

View file

@ -128,6 +128,11 @@ protected:
*/
float LocalTime() const;
/**
* Get the http interface
*/
class IHttp *Http() const;
public:
/**
* The component virtual destructor.

View file

@ -17,6 +17,7 @@
#include <engine/serverbrowser.h>
#include <engine/shared/config.h>
#include <engine/shared/http.h>
#include <engine/shared/jobs.h>
#include <engine/shared/linereader.h>
#include <engine/textrender.h>
@ -515,34 +516,35 @@ protected:
char m_aPath[IO_MAX_PATH_LENGTH];
int m_StorageType;
bool m_Success = false;
CImageInfo m_ImageInfo;
SHA256_DIGEST m_Sha256;
CAbstractCommunityIconJob(CMenus *pMenus, const char *pCommunityId, int StorageType);
virtual ~CAbstractCommunityIconJob();
virtual ~CAbstractCommunityIconJob() {};
public:
const char *CommunityId() const { return m_aCommunityId; }
bool Success() const { return m_Success; }
CImageInfo &&ImageInfo() { return std::move(m_ImageInfo); }
SHA256_DIGEST &&Sha256() { return std::move(m_Sha256); }
};
class CCommunityIconLoadJob : public IJob, public CAbstractCommunityIconJob
{
CImageInfo m_ImageInfo;
protected:
void Run() override;
public:
CCommunityIconLoadJob(CMenus *pMenus, const char *pCommunityId, int StorageType);
CImageInfo &&ImageInfo() { return std::move(m_ImageInfo); }
};
class CCommunityIconDownloadJob : public CHttpRequest, public CAbstractCommunityIconJob
{
protected:
int OnCompletion(int State) override;
public:
CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl, const SHA256_DIGEST &Sha256);
};
struct SCommunityIcon
{
char m_aCommunityId[CServerInfo::MAX_COMMUNITY_ID_LENGTH];

View file

@ -1802,25 +1802,6 @@ CMenus::CAbstractCommunityIconJob::CAbstractCommunityIconJob(CMenus *pMenus, con
str_format(m_aPath, sizeof(m_aPath), "communityicons/%s.png", pCommunityId);
}
CMenus::CAbstractCommunityIconJob::~CAbstractCommunityIconJob()
{
free(m_ImageInfo.m_pData);
m_ImageInfo.m_pData = nullptr;
}
int CMenus::CCommunityIconDownloadJob::OnCompletion(int State)
{
State = CHttpRequest::OnCompletion(State);
if(State == HTTP_DONE)
{
if(m_pMenus->LoadCommunityIconFile(Dest(), IStorage::TYPE_SAVE, m_ImageInfo, m_Sha256))
m_Success = true;
else
State = HTTP_ERROR;
}
return State;
}
CMenus::CCommunityIconDownloadJob::CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl, const SHA256_DIGEST &Sha256) :
CHttpRequest(pUrl),
CAbstractCommunityIconJob(pMenus, pCommunityId, IStorage::TYPE_SAVE)
@ -1964,10 +1945,14 @@ void CMenus::UpdateCommunityIcons()
if(!m_CommunityIconDownloadJobs.empty())
{
std::shared_ptr<CCommunityIconDownloadJob> pJob = m_CommunityIconDownloadJobs.front();
if(pJob->Status() == IJob::STATE_DONE)
if(pJob->Done())
{
if(pJob->Success())
LoadCommunityIconFinish(pJob->CommunityId(), pJob->ImageInfo(), pJob->Sha256());
if(pJob->State() == HTTP_DONE)
{
std::shared_ptr<CCommunityIconLoadJob> pLoadJob = std::make_shared<CCommunityIconLoadJob>(this, pJob->CommunityId(), IStorage::TYPE_SAVE);
Engine()->AddJob(pLoadJob);
m_CommunityIconLoadJobs.emplace_back(std::move(pLoadJob));
}
m_CommunityIconDownloadJobs.pop_front();
}
}
@ -2007,7 +1992,7 @@ void CMenus::UpdateCommunityIcons()
if(pExistingDownload == m_CommunityIconDownloadJobs.end() && (ExistingIcon == m_vCommunityIcons.end() || ExistingIcon->m_Sha256 != Community.IconSha256()))
{
std::shared_ptr<CCommunityIconDownloadJob> pJob = std::make_shared<CCommunityIconDownloadJob>(this, Community.Id(), Community.IconUrl(), Community.IconSha256());
Engine()->AddJob(pJob);
Http()->Run(pJob);
m_CommunityIconDownloadJobs.push_back(pJob);
}
}

View file

@ -21,15 +21,13 @@ bool CSkins::IsVanillaSkin(const char *pName)
return std::any_of(std::begin(VANILLA_SKINS), std::end(VANILLA_SKINS), [pName](const char *pVanillaSkin) { return str_comp(pName, pVanillaSkin) == 0; });
}
int CSkins::CGetPngFile::OnCompletion(int State)
void CSkins::CGetPngFile::OnCompletion()
{
State = CHttpRequest::OnCompletion(State);
if(State != HTTP_ERROR && State != HTTP_ABORTED && !m_pSkins->LoadSkinPNG(m_Info, Dest(), Dest(), IStorage::TYPE_SAVE))
// Maybe this should start another thread to load the png in instead of stalling the curl thread
if(State() != HTTP_ERROR && State() != HTTP_ABORTED)
{
State = HTTP_ERROR;
m_pSkins->LoadSkinPNG(m_Info, Dest(), Dest(), IStorage::TYPE_SAVE);
}
return State;
}
CSkins::CGetPngFile::CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest) :
@ -436,12 +434,16 @@ const CSkin *CSkins::FindImpl(const char *pName)
char aEscapedName[256];
EscapeUrl(aEscapedName, sizeof(aEscapedName), pName);
str_format(aUrl, sizeof(aUrl), "%s%s.png", g_Config.m_ClDownloadCommunitySkins != 0 ? g_Config.m_ClSkinCommunityDownloadUrl : g_Config.m_ClSkinDownloadUrl, aEscapedName);
char aBuf[IO_MAX_PATH_LENGTH];
str_format(Skin.m_aPath, sizeof(Skin.m_aPath), "downloadedskins/%s", IStorage::FormatTmpPath(aBuf, sizeof(aBuf), pName));
Skin.m_pTask = std::make_shared<CGetPngFile>(this, aUrl, Storage(), Skin.m_aPath);
m_pClient->Engine()->AddJob(Skin.m_pTask);
Http()->Run(Skin.m_pTask);
auto &&pDownloadSkin = std::make_unique<CDownloadSkin>(std::move(Skin));
m_DownloadSkins.insert({pDownloadSkin->GetName(), std::move(pDownloadSkin)});
++m_DownloadingSkins;
return nullptr;
}

View file

@ -20,7 +20,7 @@ public:
CSkins *m_pSkins;
protected:
virtual int OnCompletion(int State) override;
virtual void OnCompletion() override;
public:
CGetPngFile(CSkins *pSkins, const char *pUrl, IStorage *pStorage, const char *pDest);

View file

@ -100,6 +100,7 @@ void CGameClient::OnConsoleInit()
#if defined(CONF_AUTOUPDATE)
m_pUpdater = Kernel()->RequestInterface<IUpdater>();
#endif
m_pHttp = Kernel()->RequestInterface<IHttp>();
m_Menus.SetMenuBackground(&m_MenuBackground);

View file

@ -177,6 +177,7 @@ private:
#if defined(CONF_AUTOUPDATE)
class IUpdater *m_pUpdater;
#endif
class IHttp *m_pHttp;
CLayers m_Layers;
CCollision m_Collision;
@ -251,6 +252,7 @@ public:
return m_pUpdater;
}
#endif
class IHttp *Http() { return m_pHttp; }
int NetobjNumCorrections()
{