ddnet/src/engine/shared/http.h

159 lines
3.4 KiB
C
Raw Normal View History

#ifndef ENGINE_SHARED_HTTP_H
#define ENGINE_SHARED_HTTP_H
#include <atomic>
#include <engine/kernel.h>
#include <engine/shared/jobs.h>
#include <engine/storage.h>
typedef struct _json_value json_value;
typedef void CURL;
enum
{
HTTP_ERROR = -1,
HTTP_QUEUED,
HTTP_RUNNING,
HTTP_DONE,
HTTP_ABORTED,
};
enum class HTTPLOG
{
NONE,
FAILURE,
ALL,
};
2022-03-07 14:01:37 +00:00
enum class IPRESOLVE
{
WHATEVER,
V4,
V6,
};
struct CTimeout
{
long ConnectTimeoutMs;
long LowSpeedLimit;
long LowSpeedTime;
};
class CRequest : public IJob
{
// Abort the request with an error if `BeforeInit()` or `AfterInit()`
2019-12-20 21:50:01 +00:00
// returns false. Also abort the request if `OnData()` returns
// something other than `DataSize`.
virtual bool BeforeInit() { return true; }
virtual bool AfterInit(void *pCurl) { return true; }
virtual size_t OnData(char *pData, size_t DataSize) = 0;
virtual void OnProgress() {}
char m_aUrl[256];
CTimeout m_Timeout;
std::atomic<double> m_Size;
std::atomic<double> m_Current;
std::atomic<int> m_Progress;
HTTPLOG m_LogProgress;
2022-03-07 14:01:37 +00:00
IPRESOLVE m_IpResolve;
std::atomic<int> m_State;
std::atomic<bool> m_Abort;
static int ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr);
static size_t WriteCallback(char *pData, size_t Size, size_t Number, void *pUser);
void Run();
int RunImpl(CURL *pHandle);
protected:
virtual int OnCompletion(int State) { return State; }
public:
2022-03-07 14:01:37 +00:00
CRequest(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress = HTTPLOG::ALL, IPRESOLVE IpResolve = IPRESOLVE::WHATEVER);
double Current() const { return m_Current.load(std::memory_order_relaxed); }
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; }
void Abort() { m_Abort = true; }
};
Add client-side HTTP server info Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
2018-07-11 20:46:04 +00:00
class CHead : public CRequest
{
virtual size_t OnData(char *pData, size_t DataSize) { return DataSize; }
virtual bool AfterInit(void *pCurl);
public:
CHead(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress = HTTPLOG::ALL);
Add client-side HTTP server info Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
2018-07-11 20:46:04 +00:00
~CHead();
};
class CGet : public CRequest
{
virtual size_t OnData(char *pData, size_t DataSize);
size_t m_BufferSize;
size_t m_BufferLength;
unsigned char *m_pBuffer;
public:
CGet(const char *pUrl, CTimeout Timeout, HTTPLOG LogProgress = HTTPLOG::ALL);
~CGet();
size_t ResultSize() const
{
if(!Result())
{
return 0;
}
else
{
return m_BufferSize;
}
}
unsigned char *Result() const;
unsigned char *TakeResult();
json_value *ResultJson() const;
};
class CGetFile : public CRequest
{
virtual size_t OnData(char *pData, size_t DataSize);
virtual bool BeforeInit();
IStorage *m_pStorage;
char m_aDestFull[IO_MAX_PATH_LENGTH];
IOHANDLE m_File;
protected:
char m_aDest[IO_MAX_PATH_LENGTH];
int m_StorageType;
virtual int OnCompletion(int State);
public:
2022-03-07 14:01:37 +00:00
CGetFile(IStorage *pStorage, const char *pUrl, const char *pDest, int StorageType = -2, CTimeout Timeout = CTimeout{4000, 500, 5}, HTTPLOG LogProgress = HTTPLOG::ALL, IPRESOLVE IpResolve = IPRESOLVE::WHATEVER);
const char *Dest() const { return m_aDest; }
};
class CPostJson : public CRequest
{
virtual size_t OnData(char *pData, size_t DataSize) { return DataSize; }
virtual bool AfterInit(void *pCurl);
char m_aJson[1024];
public:
CPostJson(const char *pUrl, CTimeout Timeout, const char *pJson);
};
bool HttpInit(IStorage *pStorage);
void EscapeUrl(char *pBuf, int Size, const char *pStr);
#endif // ENGINE_SHARED_HTTP_H