DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
HttpsClient.cpp
Go to the documentation of this file.
1/*
2 MIT License
3
4 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
5
6 Copyright 2022, 2023 Chris M. (RealTimeChris)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26/// HttpsClient.cpp - Source file for the https class.
27/// May 12, 2021
28/// https://discordcoreapi.com
29/// \file HttpsClient.cpp
30
34
35namespace discord_core_api {
36
37 namespace discord_core_internal {
38
39 DCA_INLINE void rate_limit_queue::initialize() {
40 for (int64_t enumOne = static_cast<int64_t>(https_workload_type::Unset); enumOne != static_cast<int64_t>(https_workload_type::Last); enumOne++) {
41 auto tempBucket = jsonifier::toString(std::chrono::duration_cast<nanoseconds>(sys_clock::now().time_since_epoch()).count());
42 buckets.emplace(static_cast<https_workload_type>(enumOne), tempBucket);
43 rateLimits.emplace(tempBucket, makeUnique<rate_limit_data>())
44 .getRawPtr()
45 ->second->sampledTimeInMs.store(std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch()));
46 std::this_thread::sleep_for(1ms);
47 }
48 }
49
50 DCA_INLINE rate_limit_data* rate_limit_queue::getEndpointAccess(https_workload_type workloadType) {
51 stop_watch<milliseconds> stopWatch{ milliseconds{ 25000 } };
52 stopWatch.reset();
53 auto targetTime =
54 std::chrono::duration_cast<std::chrono::duration<int64_t, std::milli>>(rateLimits[buckets[workloadType]]->sampledTimeInMs.load(std::memory_order_acquire)) +
55 std::chrono::duration_cast<std::chrono::duration<int64_t, std::milli>>(rateLimits[buckets[workloadType]]->sRemain.load(std::memory_order_acquire));
56 if (rateLimits[buckets[workloadType]]->getsRemaining.load(std::memory_order_acquire) <= 0) {
57 auto newNow = std::chrono::duration_cast<std::chrono::duration<int64_t, std::milli>>(sys_clock::now().time_since_epoch());
58 while ((newNow - targetTime).count() <= 0) {
59 if (stopWatch.hasTimeElapsed()) {
60 return nullptr;
61 }
62 newNow = std::chrono::duration_cast<std::chrono::duration<int64_t, std::milli>>(sys_clock::now().time_since_epoch());
63 std::this_thread::sleep_for(1us);
64 }
65 }
66 stopWatch.reset();
67 while (!rateLimits[buckets[workloadType]]->accessMutex.try_lock()) {
68 std::this_thread::sleep_for(1us);
69 if (stopWatch.hasTimeElapsed()) {
70 return nullptr;
71 }
72 }
73 return rateLimits.at(buckets.at(workloadType)).get();
74 }
75
76 DCA_INLINE void rate_limit_queue::releaseEndPointAccess(https_workload_type type) {
77 rateLimits.at(buckets.at(type))->accessMutex.unlock();
78 }
79
80 jsonifier::vector<jsonifier::string_view> tokenize(jsonifier::string_view in, const char* sep = "\r\n") {
81 jsonifier::vector<jsonifier::string_view> result{};
82 jsonifier::string_view::size_type b = 0;
83 jsonifier::string_view::size_type e = 0;
84 while ((b = in.findFirstNotOf(sep, e)) != jsonifier::string_view::npos) {
85 e = in.findFirstOf(sep, b);
86 if (e == jsonifier::string_view::npos) {
87 break;
88 }
89 result.emplace_back(in.substr(b, e - b));
90 }
91 return result;
92 }
93
94 uint64_t parseCode(jsonifier::string_view string) {
95 uint64_t start = string.find(' ');
96 if (start == jsonifier::string_view::npos) {
97 return 0;
98 }
99
100 while (std::isspace(string[start])) {
101 start++;
102 }
103
104 uint64_t end = start;
105 while (std::isdigit(string[end])) {
106 end++;
107 }
108 jsonifier::string_view codeStr = string.substr(start, end - start);
109 uint64_t code = jsonifier::strToUint64(codeStr.data());
110 return code;
111 }
112
113 https_connection::https_connection(const jsonifier::string& baseUrlNew, const uint16_t portNew) : tcp_connection<https_connection>{ baseUrlNew, portNew } {
114 }
115
116 void https_connection::handleBuffer() {
117 stop_watch<milliseconds> stopWatch{ 9500 };
118 jsonifier::string_view_base<uint8_t> stringNew{};
119 stopWatch.reset();
120 do {
121 stringNew = getInputBuffer();
122 inputBufferReal += stringNew;
123 switch (data.currentState) {
124 case https_state::Collecting_Headers: {
125 if (!parseHeaders()) {
126 return;
127 }
128 break;
129 }
130 case https_state::Collecting_Contents: {
131 if (!parseContents()) {
132 return;
133 }
134 break;
135 }
136 case https_state::Collecting_Chunked_Contents: {
137 if (!parseChunk()) {
138 return;
139 }
140 break;
141 }
142 case https_state::complete: {
143 inputBufferReal.clear();
144 return;
145 }
146 }
147 } while (stringNew.size() > 0 && !stopWatch.hasTimeElapsed());
148 return;
149 }
150
151 bool https_connection::areWeConnected() {
152 return tcp_connection::areWeStillConnected();
153 }
154
155 void https_connection::disconnect() {
156 tcp_connection::disconnect();
157 tcp_connection::reset();
158 }
159
160 void https_connection::resetValues(https_workload_data&& workloadDataNew, rate_limit_data* rateLimitDataNew) {
161 currentRateLimitData = rateLimitDataNew;
162 if (currentBaseUrl != workloadDataNew.baseUrl) {
163 tcp_connection::reset();
164 currentBaseUrl = workloadDataNew.baseUrl;
165 }
166 workload = std::move(workloadDataNew);
167 if (workload.baseUrl == "") {
168 workload.baseUrl = "https://discord.com/api/v10";
169 }
170 inputBufferReal.clear();
171 data = https_response_data{};
172 }
173
174 void https_rnr_builder::updateRateLimitData(rate_limit_data& rateLimitData) {
175 auto connection{ static_cast<https_connection*>(this) };
176 if (connection->data.responseHeaders.contains("x-ratelimit-bucket")) {
177 rateLimitData.bucket = connection->data.responseHeaders.at("x-ratelimit-bucket");
178 }
179 if (connection->data.responseHeaders.contains("x-ratelimit-reset-after")) {
180 rateLimitData.sRemain.store(seconds{ static_cast<int64_t>(ceil(jsonifier::strToDouble(connection->data.responseHeaders.at("x-ratelimit-reset-after").data()))) },
181 std::memory_order_release);
182 }
183 if (connection->data.responseHeaders.contains("x-ratelimit-remaining")) {
184 rateLimitData.getsRemaining.store(static_cast<int64_t>(jsonifier::strToInt64(connection->data.responseHeaders.at("x-ratelimit-remaining").data())),
185 std::memory_order_release);
186 }
187 if (rateLimitData.getsRemaining.load(std::memory_order_acquire) <= 1 || rateLimitData.areWeASpecialBucket.load(std::memory_order_acquire)) {
188 rateLimitData.doWeWait.store(true, std::memory_order_release);
189 }
190 }
191
192 https_response_data https_rnr_builder::finalizeReturnValues(rate_limit_data& rateLimitData) {
193 auto connection{ static_cast<https_connection*>(this) };
194 if (connection->data.responseData.size() >= connection->data.contentLength && connection->data.contentLength > 0) {
195 connection->data.responseData = connection->data.responseData.substr(0, connection->data.contentLength);
196 } else {
197 auto pos1 = connection->data.responseData.findFirstOf('{');
198 auto pos2 = connection->data.responseData.findLastOf('}');
199 auto pos3 = connection->data.responseData.findFirstOf('[');
200 auto pos4 = connection->data.responseData.findLastOf(']');
201 if (pos1 != jsonifier::string_view::npos && pos2 != jsonifier::string_view::npos && pos1 < pos3) {
202 connection->data.responseData = connection->data.responseData.substr(pos1, pos2 + 1);
203 } else if (pos3 != jsonifier::string_view::npos && pos4 != jsonifier::string_view::npos) {
204 connection->data.responseData = connection->data.responseData.substr(pos3, pos4 + 1);
205 }
206 }
207 updateRateLimitData(rateLimitData);
208 if (connection->data.responseCode != 204 && connection->data.responseCode != 200 && connection->data.responseCode != 201) {
209 throw dca_exception{ "Sorry, but that https request threw the following error: " + connection->data.responseCode.operator jsonifier::string() +
210 connection->data.responseData };
211 }
212 return std::move(connection->data);
213 }
214
215 jsonifier::string https_rnr_builder::buildRequest(const https_workload_data& workload) {
216 jsonifier::string baseUrlNew{};
217 if (workload.baseUrl.find(".com") != jsonifier::string_view::npos) {
218 baseUrlNew = workload.baseUrl.substr(workload.baseUrl.find("https://") + jsonifier::string_view("https://").size(),
219 workload.baseUrl.find(".com") + jsonifier::string_view(".com").size() - jsonifier::string_view("https://").size());
220 } else if (workload.baseUrl.find(".org") != jsonifier::string_view::npos) {
221 baseUrlNew = workload.baseUrl.substr(workload.baseUrl.find("https://") + jsonifier::string_view("https://").size(),
222 workload.baseUrl.find(".org") + jsonifier::string_view(".org").size() - jsonifier::string_view("https://").size());
223 }
224 jsonifier::string returnString{};
225 if (workload.workloadClass == https_workload_class::Get || workload.workloadClass == https_workload_class::Delete) {
226 if (workload.workloadClass == https_workload_class::Get) {
227 returnString += "GET " + workload.baseUrl + workload.relativePath + " HTTP/1.1\r\n";
228 } else if (workload.workloadClass == https_workload_class::Delete) {
229 returnString += "DELETE " + workload.baseUrl + workload.relativePath + " HTTP/1.1\r\n";
230 }
231 for (auto& [key, value]: workload.headersToInsert) {
232 returnString += key + ": " + value + "\r\n";
233 }
234 returnString += "Pragma: no-cache\r\n";
235 returnString += "Connection: keep-alive\r\n";
236 returnString += "Host: " + baseUrlNew + "\r\n\r\n";
237 } else {
238 if (workload.workloadClass == https_workload_class::Patch) {
239 returnString += "PATCH " + workload.baseUrl + workload.relativePath + " HTTP/1.1\r\n";
240 } else if (workload.workloadClass == https_workload_class::Post) {
241 returnString += "POST " + workload.baseUrl + workload.relativePath + " HTTP/1.1\r\n";
242 } else if (workload.workloadClass == https_workload_class::Put) {
243 returnString = "PUT " + workload.baseUrl + workload.relativePath + " HTTP/1.1\r\n";
244 }
245 for (auto& [key, value]: workload.headersToInsert) {
246 returnString += key + ": " + value + "\r\n";
247 }
248 returnString += "Pragma: no-cache\r\n";
249 returnString += "Connection: keep-alive\r\n";
250 returnString += "Host: " + baseUrlNew + "\r\n";
251 returnString += "Content-Length: " + jsonifier::toString(workload.content.size()) + "\r\n\r\n";
252 returnString += workload.content + "\r\n\r\n";
253 }
254 return returnString;
255 }
256
257 bool https_rnr_builder::parseHeaders() {
258 auto connection{ static_cast<https_connection*>(this) };
259 jsonifier::string& stringViewNew = connection->inputBufferReal;
260 if (stringViewNew.find("\r\n\r\n") != jsonifier::string_view::npos) {
261 auto headers = tokenize(stringViewNew);
262 if (headers.size() && (headers.at(0).find("HTTP/1") != jsonifier::string_view::npos)) {
263 uint64_t parseCodeNew{};
264 try {
265 parseCodeNew = parseCode(headers.at(0));
266 } catch (const std::invalid_argument& error) {
267 message_printer::printError<print_message_type::https>(error.what());
268 connection->data.currentState = https_state::complete;
269 }
270 headers.erase(headers.begin());
271 if (headers.size() >= 3 && parseCodeNew) {
272 for (uint64_t x = 0; x < headers.size(); ++x) {
273 jsonifier::string_view::size_type sep = headers.at(x).find(": ");
274 if (sep != jsonifier::string_view::npos) {
275 jsonifier::string key = static_cast<jsonifier::string>(headers.at(x).substr(0, sep));
276 jsonifier::string_view value = headers.at(x).substr(sep + 2, headers.at(x).size());
277 for (auto& valueNew: key) {
278 valueNew = static_cast<char>(std::tolower(static_cast<int32_t>(valueNew)));
279 }
280 connection->data.responseHeaders.emplace(key, value);
281 }
282 }
283 connection->data.responseCode = parseCodeNew;
284 if (connection->data.responseCode == 302) {
285 connection->workload.baseUrl = connection->data.responseHeaders.at("location");
286 connection->disconnect();
287 return false;
288 }
289 if (connection->data.responseCode == 204) {
290 connection->data.currentState = https_state::complete;
291 } else if (connection->data.responseHeaders.contains("content-length")) {
292 connection->data.contentLength = jsonifier::strToUint64(connection->data.responseHeaders.at("content-length").data());
293 connection->data.currentState = https_state::Collecting_Contents;
294 } else {
295 connection->data.isItChunked = true;
296 connection->data.contentLength = std::numeric_limits<uint32_t>::max();
297 connection->data.currentState = https_state::Collecting_Chunked_Contents;
298 }
299 connection->inputBufferReal.erase(connection->inputBufferReal.begin() + static_cast<int64_t>(stringViewNew.find("\r\n\r\n")) + 4);
300 }
301 }
302 return true;
303 }
304 return false;
305 }
306
307 bool https_rnr_builder::parseChunk() {
308 auto connection{ static_cast<https_connection*>(this) };
309 jsonifier::string_view stringViewNew01{ connection->inputBufferReal };
310 if (auto finalPosition = stringViewNew01.find("\r\n0\r\n\r\n"); finalPosition != jsonifier::string_view::npos) {
311 uint64_t pos{ 0 };
312 while (pos < stringViewNew01.size() || connection->data.responseData.size() < connection->data.contentLength) {
313 uint64_t lineEnd = stringViewNew01.find("\r\n", pos);
314 if (lineEnd == jsonifier::string_view::npos) {
315 break;
316 }
317
318 jsonifier::string_view sizeLine{ stringViewNew01.data() + pos, lineEnd - pos };
319 uint64_t chunkSize = jsonifier::strToUint64<16>(static_cast<jsonifier::string>(sizeLine));
320 connection->data.contentLength += chunkSize;
321
322 if (chunkSize == 0) {
323 break;
324 }
325
326 pos = lineEnd + 2;
327
328 jsonifier::string_view newString{ stringViewNew01.data() + pos, chunkSize };
329 connection->data.responseData += newString;
330 pos += chunkSize + 2;
331 }
332 connection->data.currentState = https_state::complete;
333 return true;
334 }
335 return false;
336 }
337
338 bool https_rnr_builder::parseContents() {
339 auto connection{ static_cast<https_connection*>(this) };
340 if (connection->inputBufferReal.size() >= connection->data.contentLength || !connection->data.contentLength) {
341 connection->data.responseData += jsonifier::string_view{ connection->inputBufferReal.data(), connection->data.contentLength };
342 connection->data.currentState = https_state::complete;
343 return true;
344 } else {
345 return false;
346 }
347 }
348
349 https_connection_manager::https_connection_manager(rate_limit_queue* rateLimitDataQueueNew) {
350 rateLimitQueue = rateLimitDataQueueNew;
351 }
352
353 rate_limit_queue& https_connection_manager::getRateLimitQueue() {
354 return *rateLimitQueue;
355 }
356
357 https_connection& https_connection_manager::getConnection(https_workload_type workloadType) {
358 std::unique_lock lock{ accessMutex };
359 if (!httpsConnections.contains(workloadType)) {
360 httpsConnections.emplace(workloadType, makeUnique<https_connection>());
361 }
362 httpsConnections.at(workloadType)->currentReconnectTries = 0;
363 return *httpsConnections.at(workloadType).get();
364 }
365
366 https_connection_stack_holder::https_connection_stack_holder(https_connection_manager& connectionManager, https_workload_data&& workload) {
367 connection = &connectionManager.getConnection(workload.getWorkloadType());
368 rateLimitQueue = &connectionManager.getRateLimitQueue();
369 auto rateLimitData = connectionManager.getRateLimitQueue().getEndpointAccess(workload.getWorkloadType());
370 if (!rateLimitData) {
371 throw dca_exception{ "Failed to gain endpoint access." };
372 }
373 connection->resetValues(std::move(workload), rateLimitData);
374 if (!connection->areWeConnected()) {
375 *static_cast<tcp_connection<https_connection>*>(connection) = https_connection{ connection->workload.baseUrl, static_cast<uint16_t>(443) };
376 }
377 }
378
379 https_connection_stack_holder::~https_connection_stack_holder() {
380 rateLimitQueue->releaseEndPointAccess(connection->workload.getWorkloadType());
381 }
382
383 https_connection& https_connection_stack_holder::getConnection() {
384 return *connection;
385 }
386
387 https_client::https_client(jsonifier::string_view botTokenNew) : https_client_core(botTokenNew), connectionManager(&rateLimitQueue) {
388 rateLimitQueue.initialize();
389 }
390
391 https_response_data https_client::httpsRequest(https_connection& connection) {
392 https_response_data resultData = executeByRateLimitData(connection);
393 return resultData;
394 }
395
396 https_response_data https_client::executeByRateLimitData(https_connection& connection) {
397 https_response_data returnData{};
398 milliseconds timeRemaining{};
399 milliseconds currentTime = std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch());
400 if (connection.workload.workloadType == https_workload_type::Delete_Message_Old) {
401 connection.currentRateLimitData->sRemain.store(seconds{ 4 }, std::memory_order_release);
402 }
403 if (connection.workload.workloadType == https_workload_type::Post_Message || connection.workload.workloadType == https_workload_type::Patch_Message) {
404 connection.currentRateLimitData->areWeASpecialBucket.store(true, std::memory_order_release);
405 }
406 if (connection.currentRateLimitData->areWeASpecialBucket.load(std::memory_order_acquire)) {
407 connection.currentRateLimitData->sRemain.store(seconds{ static_cast<int64_t>(ceil(4.0f / 4.0f)) }, std::memory_order_release);
408 milliseconds targetTime{ connection.currentRateLimitData->sampledTimeInMs.load(std::memory_order_acquire) +
409 std::chrono::duration_cast<std::chrono::milliseconds>(connection.currentRateLimitData->sRemain.load(std::memory_order_acquire)) };
410 timeRemaining = targetTime - currentTime;
411 } else if (connection.currentRateLimitData->doWeWait.load(std::memory_order_acquire)) {
412 milliseconds targetTime{ connection.currentRateLimitData->sampledTimeInMs.load(std::memory_order_acquire) +
413 std::chrono::duration_cast<std::chrono::milliseconds>(connection.currentRateLimitData->sRemain.load(std::memory_order_acquire)) };
414 timeRemaining = targetTime - currentTime;
415 connection.currentRateLimitData->doWeWait.store(false, std::memory_order_release);
416 }
417 if (timeRemaining.count() > 0) {
418 message_printer::printSuccess<print_message_type::https>("we're waiting on rate-limit: " + jsonifier::toString(timeRemaining.count()));
419 milliseconds targetTime{ currentTime + timeRemaining };
420 while (targetTime > currentTime && targetTime.count() > 0 && currentTime.count() > 0 && timeRemaining.count() > 0) {
421 currentTime = std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch());
422 timeRemaining = targetTime - currentTime;
423 if (timeRemaining.count() <= 20) {
424 continue;
425 } else {
426 std::this_thread::sleep_for(milliseconds{ static_cast<int64_t>(static_cast<double>(timeRemaining.count()) * 80.0f / 100.0f) });
427 }
428 }
429 }
430 returnData = https_client::httpsRequestInternal(connection);
431 connection.currentRateLimitData->sampledTimeInMs.store(std::chrono::duration_cast<std::chrono::duration<int64_t, std::milli>>(sys_clock::now().time_since_epoch()),
432 std::memory_order_release);
433
434 if (returnData.responseCode == 204 || returnData.responseCode == 201 || returnData.responseCode == 200) {
435 message_printer::printSuccess<print_message_type::https>(
436 connection.workload.callStack + " success: " + static_cast<jsonifier::string>(returnData.responseCode) + ": " + returnData.responseData);
437 } else if (returnData.responseCode == 429) {
438 if (connection.data.responseHeaders.contains("x-ratelimit-retry-after")) {
439 connection.currentRateLimitData->sRemain.store(seconds{ jsonifier::strToInt64(connection.data.responseHeaders.at("x-ratelimit-retry-after").data()) / 1000LL },
440 std::memory_order_release);
441 }
442 connection.currentRateLimitData->doWeWait.store(true, std::memory_order_release);
443 connection.currentRateLimitData->sampledTimeInMs.store(std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch()), std::memory_order_release);
444 message_printer::printError<print_message_type::https>(connection.workload.callStack + "::httpsRequest(), we've hit rate limit! time remaining: " +
445 jsonifier::toString(connection.currentRateLimitData->sRemain.load(std::memory_order_acquire).count()));
446 connection.resetValues(std::move(connection.workload), connection.currentRateLimitData);
447 returnData = executeByRateLimitData(connection);
448 }
449 return returnData;
450 }
451
452 https_client_core::https_client_core(jsonifier::string_view botTokenNew) {
453 botToken = botTokenNew;
454 }
455
456 https_response_data https_client_core::httpsRequestInternal(https_connection& connection) {
457 if (connection.workload.baseUrl == "https://discord.com/api/v10") {
458 connection.workload.headersToInsert.emplace("Authorization", "Bot " + botToken);
459 connection.workload.headersToInsert.emplace("User-Agent", "DiscordCoreAPI (https://discordcoreapi.com/1.0)");
460 if (connection.workload.payloadType == payload_type::Application_Json) {
461 connection.workload.headersToInsert.emplace("Content-Type", "application/json");
462 } else if (connection.workload.payloadType == payload_type::Multipart_Form) {
463 connection.workload.headersToInsert.emplace("Content-Type", "multipart/form-data; boundary=boundary25");
464 }
465 }
466 if (connection.currentReconnectTries >= connection.maxReconnectTries) {
467 connection.disconnect();
468 return https_response_data{};
469 }
470 if (!connection.areWeConnected()) {
471 connection.currentBaseUrl = connection.workload.baseUrl;
472 *static_cast<tcp_connection<https_connection>*>(&connection) = https_connection{ connection.workload.baseUrl, static_cast<uint16_t>(443) };
473 if (connection.currentStatus != connection_status::NO_Error || !connection.areWeConnected()) {
474 ++connection.currentReconnectTries;
475 connection.disconnect();
476 return httpsRequestInternal(connection);
477 }
478 }
479 auto request = connection.buildRequest(connection.workload);
480 if (connection.areWeConnected()) {
481 connection.writeData(static_cast<jsonifier::string_view>(request), true);
482 if (connection.currentStatus != connection_status::NO_Error || !connection.areWeConnected()) {
483 ++connection.currentReconnectTries;
484 connection.disconnect();
485 return httpsRequestInternal(connection);
486 }
487 auto result = getResponse(connection);
488 if (static_cast<int64_t>(result.responseCode) == -1 || !connection.areWeConnected()) {
489 ++connection.currentReconnectTries;
490 connection.disconnect();
491 return httpsRequestInternal(connection);
492 } else {
493 return result;
494 }
495 } else {
496 ++connection.currentReconnectTries;
497 connection.disconnect();
498 return httpsRequestInternal(connection);
499 }
500 }
501
502 https_response_data https_client_core::recoverFromError(https_connection& connection) {
503 if (connection.currentReconnectTries >= connection.maxReconnectTries) {
504 connection.disconnect();
505 return connection.finalizeReturnValues(*connection.currentRateLimitData);
506 }
507 ++connection.currentReconnectTries;
508 connection.disconnect();
509 std::this_thread::sleep_for(150ms);
510 return httpsRequestInternal(connection);
511 }
512
513 https_response_data https_client_core::getResponse(https_connection& connection) {
514 while (connection.data.currentState != https_state::complete) {
515 if (connection.areWeConnected()) {
516 auto newState = connection.processIO(10);
517 switch (newState) {
518 case connection_status::NO_Error: {
519 continue;
520 }
521 case connection_status::CONNECTION_Error:
522 [[fallthrough]];
523 case connection_status::POLLERR_Error:
524 [[fallthrough]];
525 case connection_status::POLLHUP_Error:
526 [[fallthrough]];
527 case connection_status::POLLNVAL_Error:
528 [[fallthrough]];
529 case connection_status::READ_Error:
530 [[fallthrough]];
531 case connection_status::WRITE_Error:
532 [[fallthrough]];
533 case connection_status::SOCKET_Error:
534 [[fallthrough]];
535 default: {
536 return recoverFromError(connection);
537 }
538 }
539 } else {
540 return recoverFromError(connection);
541 }
542 }
543 return connection.finalizeReturnValues(*connection.currentRateLimitData);
544 }
545 }
546}
DCA_INLINE unique_ptr< value_type, deleter > makeUnique(arg_types &&... args)
Helper function to create a unique_ptr for a non-array object.
The main namespace for the forward-facing interfaces.