37 namespace discord_core_internal {
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);
45 ->second->sampledTimeInMs.store(std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch()));
46 std::this_thread::sleep_for(1ms);
50 DCA_INLINE rate_limit_data* rate_limit_queue::getEndpointAccess(https_workload_type workloadType) {
51 stop_watch<milliseconds> stopWatch{ milliseconds{ 25000 } };
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()) {
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);
67 while (!rateLimits[buckets[workloadType]]->accessMutex.try_lock()) {
68 std::this_thread::sleep_for(1us);
69 if (stopWatch.hasTimeElapsed()) {
73 return rateLimits.at(buckets.at(workloadType)).get();
76 DCA_INLINE
void rate_limit_queue::releaseEndPointAccess(https_workload_type type) {
77 rateLimits.at(buckets.at(type))->accessMutex.unlock();
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) {
89 result.emplace_back(in.substr(b, e - b));
94 uint64_t parseCode(jsonifier::string_view
string) {
95 uint64_t start =
string.find(
' ');
96 if (start == jsonifier::string_view::npos) {
100 while (std::isspace(
string[start])) {
104 uint64_t end = start;
105 while (std::isdigit(
string[end])) {
108 jsonifier::string_view codeStr =
string.substr(start, end - start);
109 uint64_t code = jsonifier::strToUint64(codeStr.data());
113 https_connection::https_connection(
const jsonifier::string& baseUrlNew,
const uint16_t portNew) : tcp_connection<https_connection>{ baseUrlNew, portNew } {
116 void https_connection::handleBuffer() {
117 stop_watch<milliseconds> stopWatch{ 9500 };
118 jsonifier::string_view_base<uint8_t> stringNew{};
121 stringNew = getInputBuffer();
122 inputBufferReal += stringNew;
123 switch (data.currentState) {
124 case https_state::Collecting_Headers: {
125 if (!parseHeaders()) {
130 case https_state::Collecting_Contents: {
131 if (!parseContents()) {
136 case https_state::Collecting_Chunked_Contents: {
142 case https_state::complete: {
143 inputBufferReal.clear();
147 }
while (stringNew.size() > 0 && !stopWatch.hasTimeElapsed());
151 bool https_connection::areWeConnected() {
152 return tcp_connection::areWeStillConnected();
155 void https_connection::disconnect() {
156 tcp_connection::disconnect();
157 tcp_connection::reset();
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;
166 workload = std::move(workloadDataNew);
167 if (workload.baseUrl ==
"") {
168 workload.baseUrl =
"https://discord.com/api/v10";
170 inputBufferReal.clear();
171 data = https_response_data{};
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");
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);
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);
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);
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);
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);
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 };
212 return std::move(connection->data);
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());
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";
231 for (
auto& [key, value]: workload.headersToInsert) {
232 returnString += key +
": " + value +
"\r\n";
234 returnString +=
"Pragma: no-cache\r\n";
235 returnString +=
"Connection: keep-alive\r\n";
236 returnString +=
"Host: " + baseUrlNew +
"\r\n\r\n";
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";
245 for (
auto& [key, value]: workload.headersToInsert) {
246 returnString += key +
": " + value +
"\r\n";
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";
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{};
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;
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)));
280 connection->data.responseHeaders.emplace(key, value);
283 connection->data.responseCode = parseCodeNew;
284 if (connection->data.responseCode == 302) {
285 connection->workload.baseUrl = connection->data.responseHeaders.at(
"location");
286 connection->disconnect();
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;
295 connection->data.isItChunked =
true;
296 connection->data.contentLength = std::numeric_limits<uint32_t>::max();
297 connection->data.currentState = https_state::Collecting_Chunked_Contents;
299 connection->inputBufferReal.erase(connection->inputBufferReal.begin() +
static_cast<int64_t
>(stringViewNew.find(
"\r\n\r\n")) + 4);
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) {
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) {
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;
322 if (chunkSize == 0) {
328 jsonifier::string_view newString{ stringViewNew01.data() + pos, chunkSize };
329 connection->data.responseData += newString;
330 pos += chunkSize + 2;
332 connection->data.currentState = https_state::complete;
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;
349 https_connection_manager::https_connection_manager(rate_limit_queue* rateLimitDataQueueNew) {
350 rateLimitQueue = rateLimitDataQueueNew;
353 rate_limit_queue& https_connection_manager::getRateLimitQueue() {
354 return *rateLimitQueue;
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>());
362 httpsConnections.at(workloadType)->currentReconnectTries = 0;
363 return *httpsConnections.at(workloadType).get();
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." };
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) };
379 https_connection_stack_holder::~https_connection_stack_holder() {
380 rateLimitQueue->releaseEndPointAccess(connection->workload.getWorkloadType());
383 https_connection& https_connection_stack_holder::getConnection() {
387 https_client::https_client(jsonifier::string_view botTokenNew) : https_client_core(botTokenNew), connectionManager(&rateLimitQueue) {
388 rateLimitQueue.initialize();
391 https_response_data https_client::httpsRequest(https_connection& connection) {
392 https_response_data resultData = executeByRateLimitData(connection);
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);
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);
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);
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) {
426 std::this_thread::sleep_for(milliseconds{
static_cast<int64_t
>(
static_cast<double>(timeRemaining.count()) * 80.0f / 100.0f) });
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);
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);
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);
452 https_client_core::https_client_core(jsonifier::string_view botTokenNew) {
453 botToken = botTokenNew;
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");
466 if (connection.currentReconnectTries >= connection.maxReconnectTries) {
467 connection.disconnect();
468 return https_response_data{};
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);
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);
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);
496 ++connection.currentReconnectTries;
497 connection.disconnect();
498 return httpsRequestInternal(connection);
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);
507 ++connection.currentReconnectTries;
508 connection.disconnect();
509 std::this_thread::sleep_for(150ms);
510 return httpsRequestInternal(connection);
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);
518 case connection_status::NO_Error: {
521 case connection_status::CONNECTION_Error:
523 case connection_status::POLLERR_Error:
525 case connection_status::POLLHUP_Error:
527 case connection_status::POLLNVAL_Error:
529 case connection_status::READ_Error:
531 case connection_status::WRITE_Error:
533 case connection_status::SOCKET_Error:
536 return recoverFromError(connection);
540 return recoverFromError(connection);
543 return connection.finalizeReturnValues(*connection.currentRateLimitData);
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.