1#ifndef ENGINE_SHARED_HTTP_H
2#define ENGINE_SHARED_HTTP_H
3
4#include <base/hash_ctxt.h>
5
6#include <engine/shared/jobs.h>
7
8#include <algorithm>
9#include <atomic>
10#include <condition_variable>
11#include <deque>
12#include <mutex>
13#include <optional>
14#include <unordered_map>
15
16#include <engine/http.h>
17
18typedef struct _json_value json_value;
19class IStorage;
20
21enum class EHttpState
22{
23 ERROR = -1,
24 QUEUED,
25 RUNNING,
26 DONE,
27 ABORTED,
28};
29
30enum class HTTPLOG
31{
32 NONE,
33 FAILURE,
34 ALL,
35};
36
37enum class IPRESOLVE
38{
39 WHATEVER,
40 V4,
41 V6,
42};
43
44struct CTimeout
45{
46 long ConnectTimeoutMs;
47 long TimeoutMs;
48 long LowSpeedLimit;
49 long LowSpeedTime;
50};
51
52class CHttpRequest : public IHttpRequest
53{
54 friend class CHttp;
55
56 enum class REQUEST
57 {
58 GET = 0,
59 HEAD,
60 POST,
61 POST_JSON,
62 };
63
64 static constexpr const char *GetRequestType(REQUEST Type)
65 {
66 switch(Type)
67 {
68 case REQUEST::GET:
69 return "GET";
70 case REQUEST::HEAD:
71 return "HEAD";
72 case REQUEST::POST:
73 case REQUEST::POST_JSON:
74 return "POST";
75 }
76
77 // Unreachable, maybe assert instead?
78 return "UNKNOWN";
79 }
80
81 char m_aUrl[256] = {0};
82
83 void *m_pHeaders = nullptr;
84 unsigned char *m_pBody = nullptr;
85 size_t m_BodyLength = 0;
86
87 CTimeout m_Timeout = CTimeout{.ConnectTimeoutMs: 0, .TimeoutMs: 0, .LowSpeedLimit: 0, .LowSpeedTime: 0};
88 int64_t m_MaxResponseSize = -1;
89 REQUEST m_Type = REQUEST::GET;
90
91 SHA256_DIGEST m_ActualSha256 = SHA256_ZEROED;
92 SHA256_CTX m_ActualSha256Ctx;
93 SHA256_DIGEST m_ExpectedSha256 = SHA256_ZEROED;
94
95 bool m_WriteToFile = false;
96
97 uint64_t m_ResponseLength = 0;
98
99 // If `m_WriteToFile` is false.
100 size_t m_BufferSize = 0;
101 unsigned char *m_pBuffer = nullptr;
102
103 // If `m_WriteToFile` is true.
104 IOHANDLE m_File = nullptr;
105 char m_aDestAbsolute[IO_MAX_PATH_LENGTH] = {0};
106 char m_aDest[IO_MAX_PATH_LENGTH] = {0};
107
108 std::atomic<double> m_Size{0.0};
109 std::atomic<double> m_Current{0.0};
110 std::atomic<int> m_Progress{0};
111 HTTPLOG m_LogProgress = HTTPLOG::ALL;
112 IPRESOLVE m_IpResolve = IPRESOLVE::WHATEVER;
113
114 bool m_FailOnErrorStatus = true;
115
116 char m_aErr[256]; // 256 == CURL_ERROR_SIZE
117 std::atomic<EHttpState> m_State{EHttpState::QUEUED};
118 std::atomic<bool> m_Abort{false};
119
120 int m_StatusCode = 0;
121 bool m_HeadersEnded = false;
122 std::optional<int64_t> m_ResultDate = {};
123 std::optional<int64_t> m_ResultLastModified = {};
124
125 // Abort the request with an error if `BeforeInit()` returns false.
126 bool BeforeInit();
127 bool ConfigureHandle(void *pHandle); // void * == CURL *
128 // `pHandle` can be nullptr if no handle was ever created for this request.
129 void OnCompletionInternal(void *pHandle, unsigned int Result); // void * == CURL *, unsigned int == CURLcode
130
131 // Abort the request if `OnHeader()` returns something other than
132 // `DataSize`. `pHeader` is NOT null-terminated.
133 size_t OnHeader(char *pHeader, size_t HeaderSize);
134 // Abort the request if `OnData()` returns something other than
135 // `DataSize`.
136 size_t OnData(char *pData, size_t DataSize);
137
138 static int ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr);
139 static size_t HeaderCallback(char *pData, size_t Size, size_t Number, void *pUser);
140 static size_t WriteCallback(char *pData, size_t Size, size_t Number, void *pUser);
141
142protected:
143 // These run on the curl thread now, DO NOT STALL THE THREAD
144 virtual void OnProgress() {}
145 virtual void OnCompletion(EHttpState State) {}
146
147public:
148 CHttpRequest(const char *pUrl);
149 virtual ~CHttpRequest();
150
151 void Timeout(CTimeout Timeout) { m_Timeout = Timeout; }
152 void MaxResponseSize(int64_t MaxResponseSize) { m_MaxResponseSize = MaxResponseSize; }
153 void LogProgress(HTTPLOG LogProgress) { m_LogProgress = LogProgress; }
154 void IpResolve(IPRESOLVE IpResolve) { m_IpResolve = IpResolve; }
155 void FailOnErrorStatus(bool FailOnErrorStatus) { m_FailOnErrorStatus = FailOnErrorStatus; }
156 void WriteToFile(IStorage *pStorage, const char *pDest, int StorageType);
157 void ExpectSha256(const SHA256_DIGEST &Sha256) { m_ExpectedSha256 = Sha256; }
158 void Head() { m_Type = REQUEST::HEAD; }
159 void Post(const unsigned char *pData, size_t DataLength)
160 {
161 m_Type = REQUEST::POST;
162 m_BodyLength = DataLength;
163 m_pBody = (unsigned char *)malloc(size: std::max(a: (size_t)1, b: DataLength));
164 mem_copy(dest: m_pBody, source: pData, size: DataLength);
165 }
166 void PostJson(const char *pJson)
167 {
168 m_Type = REQUEST::POST_JSON;
169 m_BodyLength = str_length(str: pJson);
170 m_pBody = (unsigned char *)malloc(size: m_BodyLength);
171 mem_copy(dest: m_pBody, source: pJson, size: m_BodyLength);
172 }
173 void Header(const char *pNameColonValue);
174 void HeaderString(const char *pName, const char *pValue)
175 {
176 char aHeader[256];
177 str_format(buffer: aHeader, buffer_size: sizeof(aHeader), format: "%s: %s", pName, pValue);
178 Header(pNameColonValue: aHeader);
179 }
180 void HeaderInt(const char *pName, int Value)
181 {
182 char aHeader[256];
183 str_format(buffer: aHeader, buffer_size: sizeof(aHeader), format: "%s: %d", pName, Value);
184 Header(pNameColonValue: aHeader);
185 }
186
187 const char *Dest()
188 {
189 if(m_WriteToFile)
190 {
191 return m_aDest;
192 }
193 else
194 {
195 return nullptr;
196 }
197 }
198
199 double Current() const { return m_Current.load(m: std::memory_order_relaxed); }
200 double Size() const { return m_Size.load(m: std::memory_order_relaxed); }
201 int Progress() const { return m_Progress.load(m: std::memory_order_relaxed); }
202 EHttpState State() const { return m_State; }
203 bool Done() const
204 {
205 EHttpState State = m_State;
206 return State != EHttpState::QUEUED && State != EHttpState::RUNNING;
207 }
208 void Abort() { m_Abort = true; }
209
210 void Wait();
211
212 void Result(unsigned char **ppResult, size_t *pResultLength) const;
213 json_value *ResultJson() const;
214 const SHA256_DIGEST &ResultSha256() const;
215
216 int StatusCode() const;
217 std::optional<int64_t> ResultAgeSeconds() const;
218 std::optional<int64_t> ResultLastModified() const;
219};
220
221inline std::unique_ptr<CHttpRequest> HttpHead(const char *pUrl)
222{
223 auto pResult = std::make_unique<CHttpRequest>(args&: pUrl);
224 pResult->Head();
225 return pResult;
226}
227
228inline std::unique_ptr<CHttpRequest> HttpGet(const char *pUrl)
229{
230 return std::make_unique<CHttpRequest>(args&: pUrl);
231}
232
233inline std::unique_ptr<CHttpRequest> HttpGetFile(const char *pUrl, IStorage *pStorage, const char *pOutputFile, int StorageType)
234{
235 std::unique_ptr<CHttpRequest> pResult = HttpGet(pUrl);
236 pResult->WriteToFile(pStorage, pDest: pOutputFile, StorageType);
237 pResult->Timeout(Timeout: CTimeout{.ConnectTimeoutMs: 4000, .TimeoutMs: 0, .LowSpeedLimit: 500, .LowSpeedTime: 5});
238 return pResult;
239}
240
241inline std::unique_ptr<CHttpRequest> HttpPost(const char *pUrl, const unsigned char *pData, size_t DataLength)
242{
243 auto pResult = std::make_unique<CHttpRequest>(args&: pUrl);
244 pResult->Post(pData, DataLength);
245 pResult->Timeout(Timeout: CTimeout{.ConnectTimeoutMs: 4000, .TimeoutMs: 15000, .LowSpeedLimit: 500, .LowSpeedTime: 5});
246 return pResult;
247}
248
249inline std::unique_ptr<CHttpRequest> HttpPostJson(const char *pUrl, const char *pJson)
250{
251 auto pResult = std::make_unique<CHttpRequest>(args&: pUrl);
252 pResult->PostJson(pJson);
253 pResult->Timeout(Timeout: CTimeout{.ConnectTimeoutMs: 4000, .TimeoutMs: 15000, .LowSpeedLimit: 500, .LowSpeedTime: 5});
254 return pResult;
255}
256
257void EscapeUrl(char *pBuf, int Size, const char *pStr);
258bool HttpHasIpresolveBug();
259
260// In an ideal world this would be a kernel interface
261class CHttp : public IHttp
262{
263 enum EState
264 {
265 UNINITIALIZED,
266 RUNNING,
267 ERROR,
268 };
269
270 void *m_pThread = nullptr;
271
272 std::mutex m_Lock{};
273 std::condition_variable m_Cv{};
274 std::atomic<EState> m_State = UNINITIALIZED;
275 std::deque<std::shared_ptr<CHttpRequest>> m_PendingRequests{};
276 std::unordered_map<void *, std::shared_ptr<CHttpRequest>> m_RunningRequests{}; // void * == CURL *
277 std::chrono::milliseconds m_ShutdownDelay{};
278 std::optional<std::chrono::time_point<std::chrono::steady_clock>> m_ShutdownTime{};
279 std::atomic<bool> m_Shutdown = false;
280
281 // Only to be used with curl_multi_wakeup
282 void *m_pMultiH = nullptr; // void * == CURLM *
283
284 static void ThreadMain(void *pUser);
285 void RunLoop();
286
287public:
288 // Startup
289 bool Init(std::chrono::milliseconds ShutdownDelay);
290
291 // User
292 virtual void Run(std::shared_ptr<IHttpRequest> pRequest) override;
293 void Shutdown() override;
294 ~CHttp();
295};
296
297#endif // ENGINE_SHARED_HTTP_H
298