ddnet/src/engine/shared/http.h

199 lines
4.8 KiB
C
Raw Normal View History

#ifndef ENGINE_SHARED_HTTP_H
#define ENGINE_SHARED_HTTP_H
#include <algorithm>
#include <atomic>
#include <engine/shared/jobs.h>
typedef struct _json_value json_value;
class IStorage;
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 TimeoutMs;
long LowSpeedLimit;
long LowSpeedTime;
};
class CHttpRequest : public IJob
{
enum class REQUEST
{
GET = 0,
HEAD,
POST,
POST_JSON,
};
char m_aUrl[256] = {0};
void *m_pHeaders = nullptr;
unsigned char *m_pBody = nullptr;
size_t m_BodyLength = 0;
CTimeout m_Timeout = CTimeout{0, 0, 0, 0};
int64_t m_MaxResponseSize = -1;
REQUEST m_Type = REQUEST::GET;
bool m_WriteToFile = false;
uint64_t m_ResponseLength = 0;
// If `m_WriteToFile` is false.
size_t m_BufferSize = 0;
unsigned char *m_pBuffer = nullptr;
// If `m_WriteToFile` is true.
IOHANDLE m_File = nullptr;
char m_aDestAbsolute[IO_MAX_PATH_LENGTH] = {0};
char m_aDest[IO_MAX_PATH_LENGTH] = {0};
std::atomic<double> m_Size{0.0};
std::atomic<double> m_Current{0.0};
std::atomic<int> m_Progress{0};
HTTPLOG m_LogProgress = HTTPLOG::ALL;
IPRESOLVE m_IpResolve = IPRESOLVE::WHATEVER;
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);
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
// Abort the request if `OnData()` returns something other than
// `DataSize`.
size_t OnData(char *pData, size_t DataSize);
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
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);
protected:
virtual void OnProgress() {}
virtual int OnCompletion(int State);
public:
CHttpRequest(const char *pUrl);
~CHttpRequest();
void Timeout(CTimeout Timeout) { m_Timeout = Timeout; }
void MaxResponseSize(int64_t MaxResponseSize) { m_MaxResponseSize = MaxResponseSize; }
void LogProgress(HTTPLOG LogProgress) { m_LogProgress = LogProgress; }
void IpResolve(IPRESOLVE IpResolve) { m_IpResolve = IpResolve; }
void WriteToFile(IStorage *pStorage, const char *pDest, int StorageType);
void Head() { m_Type = REQUEST::HEAD; }
void Post(const unsigned char *pData, size_t DataLength)
{
m_Type = REQUEST::POST;
m_BodyLength = DataLength;
m_pBody = (unsigned char *)malloc(std::max((size_t)1, DataLength));
mem_copy(m_pBody, pData, DataLength);
}
void PostJson(const char *pJson)
{
m_Type = REQUEST::POST_JSON;
m_BodyLength = str_length(pJson);
m_pBody = (unsigned char *)malloc(m_BodyLength);
mem_copy(m_pBody, pJson, m_BodyLength);
}
void Header(const char *pNameColonValue);
void HeaderString(const char *pName, const char *pValue)
{
char aHeader[256];
str_format(aHeader, sizeof(aHeader), "%s: %s", pName, pValue);
Header(aHeader);
}
void HeaderInt(const char *pName, int Value)
{
char aHeader[256];
str_format(aHeader, sizeof(aHeader), "%s: %d", pName, Value);
Header(aHeader);
}
const char *Dest()
{
if(m_WriteToFile)
{
return m_aDest;
}
else
{
return nullptr;
}
}
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; }
void Result(unsigned char **ppResult, size_t *pResultLength) const;
json_value *ResultJson() const;
};
inline std::unique_ptr<CHttpRequest> HttpHead(const char *pUrl)
{
auto pResult = std::make_unique<CHttpRequest>(pUrl);
pResult->Head();
return pResult;
}
inline std::unique_ptr<CHttpRequest> HttpGet(const char *pUrl)
{
return std::make_unique<CHttpRequest>(pUrl);
}
inline std::unique_ptr<CHttpRequest> HttpGetFile(const char *pUrl, IStorage *pStorage, const char *pOutputFile, int StorageType)
{
std::unique_ptr<CHttpRequest> pResult = HttpGet(pUrl);
pResult->WriteToFile(pStorage, pOutputFile, StorageType);
pResult->Timeout(CTimeout{4000, 0, 500, 5});
return pResult;
}
inline std::unique_ptr<CHttpRequest> HttpPost(const char *pUrl, const unsigned char *pData, size_t DataLength)
{
auto pResult = std::make_unique<CHttpRequest>(pUrl);
pResult->Post(pData, DataLength);
pResult->Timeout(CTimeout{4000, 15000, 500, 5});
return pResult;
}
inline std::unique_ptr<CHttpRequest> HttpPostJson(const char *pUrl, const char *pJson)
{
auto pResult = std::make_unique<CHttpRequest>(pUrl);
pResult->PostJson(pJson);
pResult->Timeout(CTimeout{4000, 15000, 500, 5});
return pResult;
}
bool HttpInit(IStorage *pStorage);
void EscapeUrl(char *pBuf, int Size, const char *pStr);
bool HttpHasIpresolveBug();
#endif // ENGINE_SHARED_HTTP_H