37 namespace discord_core_internal {
39 https_connection::https_connection(
const jsonifier::string& baseUrlNew,
const uint16_t portNew)
40 : tcp_connection<https_connection>{ baseUrlNew, portNew } {
43 jsonifier::vector<jsonifier::string_view> tokenize(jsonifier::string_view in,
const char* sep =
"\r\n") {
44 jsonifier::string_view::size_type b = 0;
45 jsonifier::vector<jsonifier::string_view> result{};
47 while ((b = in.findFirstNotOf(sep, b)) != jsonifier::string_view::npos) {
48 auto e = in.find(sep, b);
49 if (b + (e - b) > in.size()) {
52 result.emplace_back(in.substr(b, e - b));
58 uint64_t parseCode(jsonifier::string_view
string) {
59 uint64_t start =
string.find(
' ');
60 if (start == jsonifier::string_view::npos) {
64 while (std::isspace(
string[start])) {
69 while (std::isdigit(
string[end])) {
72 jsonifier::string_view codeStr =
string.substr(start, end - start);
73 uint64_t code = jsonifier::strToUint64(codeStr.data());
77 void https_connection::handleBuffer() {
79 inputBufferReal += getInputBuffer();
80 switch (data.currentState) {
81 case https_state::Collecting_Headers: {
82 if (!parseHeaders()) {
87 case https_state::Collecting_Contents: {
88 if (!parseContents()) {
93 case https_state::Collecting_Chunked_Contents: {
99 case https_state::complete: {
100 inputBufferReal.clear();
104 }
while (inputBufferReal.size() > 0);
108 https_client_core::https_client_core(jsonifier::string_view botTokenNew) {
109 botToken = botTokenNew;
112 void https_rnr_builder::updateRateLimitData(rate_limit_data& rateLimitData) {
113 auto connection{
static_cast<https_connection*
>(
this) };
114 if (connection->data.responseHeaders.contains(
"x-ratelimit-bucket")) {
115 rateLimitData.bucket = connection->data.responseHeaders.at(
"x-ratelimit-bucket");
117 if (connection->data.responseHeaders.contains(
"x-ratelimit-reset-after")) {
118 rateLimitData.sRemain.store(seconds{
static_cast<int64_t
>(ceil(jsonifier::strToDouble(connection->data.responseHeaders.at(
"x-ratelimit-reset-after").data()))) },
119 std::memory_order_release);
121 if (connection->data.responseHeaders.contains(
"x-ratelimit-remaining")) {
122 rateLimitData.getsRemaining.store(
static_cast<int64_t
>(jsonifier::strToInt64(connection->data.responseHeaders.at(
"x-ratelimit-remaining").data())),
123 std::memory_order_release);
125 if (rateLimitData.getsRemaining.load(std::memory_order_acquire) <= 1 || rateLimitData.areWeASpecialBucket.load(std::memory_order_acquire)) {
126 rateLimitData.doWeWait.store(
true, std::memory_order_release);
130 https_response_data https_rnr_builder::finalizeReturnValues(rate_limit_data& rateLimitData) {
131 auto connection{
static_cast<https_connection*
>(
this) };
132 if (connection->data.responseData.size() >= connection->data.contentLength && connection->data.contentLength > 0) {
133 connection->data.responseData = connection->data.responseData.substr(0, connection->data.contentLength);
135 auto pos1 = connection->data.responseData.findFirstOf(
'{');
136 auto pos2 = connection->data.responseData.findLastOf(
'}');
137 auto pos3 = connection->data.responseData.findFirstOf(
'[');
138 auto pos4 = connection->data.responseData.findLastOf(
']');
139 if (pos1 != jsonifier::string_view::npos && pos2 != jsonifier::string_view::npos && pos1 < pos3) {
140 connection->data.responseData = connection->data.responseData.substr(pos1, pos2 + 1);
141 }
else if (pos3 != jsonifier::string_view::npos && pos4 != jsonifier::string_view::npos) {
142 connection->data.responseData = connection->data.responseData.substr(pos3, pos4 + 1);
145 updateRateLimitData(rateLimitData);
146 return std::move(connection->data);
149 jsonifier::string https_rnr_builder::buildRequest(
const https_workload_data& workload) {
150 jsonifier::string baseUrlNew{};
151 if (workload.baseUrl.find(
".com") != jsonifier::string_view::npos) {
152 baseUrlNew = workload.baseUrl.substr(workload.baseUrl.find(
"https://") + jsonifier::string_view(
"https://").size(),
153 workload.baseUrl.find(
".com") + jsonifier::string_view(
".com").size() - jsonifier::string_view(
"https://").size());
154 }
else if (workload.baseUrl.find(
".org") != jsonifier::string_view::npos) {
155 baseUrlNew = workload.baseUrl.substr(workload.baseUrl.find(
"https://") + jsonifier::string_view(
"https://").size(),
156 workload.baseUrl.find(
".org") + jsonifier::string_view(
".org").size() - jsonifier::string_view(
"https://").size());
158 jsonifier::string returnString{};
159 if (workload.workloadClass == https_workload_class::Get || workload.workloadClass == https_workload_class::Delete) {
160 if (workload.workloadClass == https_workload_class::Get) {
161 returnString +=
"GET " + workload.baseUrl + workload.relativePath +
" HTTP/1.1\r\n";
162 }
else if (workload.workloadClass == https_workload_class::Delete) {
163 returnString +=
"DELETE " + workload.baseUrl + workload.relativePath +
" HTTP/1.1\r\n";
165 for (
auto& [key, value]: workload.headersToInsert) {
166 returnString += key +
": " + value +
"\r\n";
168 returnString +=
"Pragma: no-cache\r\n";
169 returnString +=
"Connection: keep-alive\r\n";
170 returnString +=
"Host: " + baseUrlNew +
"\r\n\r\n";
172 if (workload.workloadClass == https_workload_class::Patch) {
173 returnString +=
"PATCH " + workload.baseUrl + workload.relativePath +
" HTTP/1.1\r\n";
174 }
else if (workload.workloadClass == https_workload_class::Post) {
175 returnString +=
"POST " + workload.baseUrl + workload.relativePath +
" HTTP/1.1\r\n";
176 }
else if (workload.workloadClass == https_workload_class::Put) {
177 returnString =
"PUT " + workload.baseUrl + workload.relativePath +
" HTTP/1.1\r\n";
179 for (
auto& [key, value]: workload.headersToInsert) {
180 returnString += key +
": " + value +
"\r\n";
182 returnString +=
"Pragma: no-cache\r\n";
183 returnString +=
"Connection: keep-alive\r\n";
184 returnString +=
"Host: " + baseUrlNew +
"\r\n";
185 returnString +=
"Content-Length: " + jsonifier::toString(workload.content.size()) +
"\r\n\r\n";
186 returnString += workload.content;
191 bool https_rnr_builder::parseHeaders() {
192 auto connection{
static_cast<https_connection*
>(
this) };
193 jsonifier::string& stringViewNew = connection->inputBufferReal;
194 if (stringViewNew.find(
"\r\n\r\n") != jsonifier::string_view::npos) {
195 auto headers = tokenize(stringViewNew);
196 if (headers.size() && (headers.at(0).find(
"HTTP/1") != jsonifier::string_view::npos)) {
197 uint64_t parseCodeNew{};
199 parseCodeNew = parseCode(headers.at(0));
200 }
catch (
const std::invalid_argument& error) {
201 message_printer::printError<print_message_type::https>(error.what());
202 connection->data.currentState = https_state::complete;
204 headers.erase(headers.begin());
205 if (headers.size() >= 3 && parseCodeNew) {
206 for (uint64_t x = 0; x < headers.size(); ++x) {
207 jsonifier::string_view::size_type sep = headers.at(x).find(
": ");
208 if (sep != jsonifier::string_view::npos) {
209 jsonifier::string key =
static_cast<jsonifier::string
>(headers.at(x).substr(0, sep));
210 jsonifier::string_view value = headers.at(x).substr(sep + 2, headers.at(x).size());
211 for (
auto& valueNew: key) {
212 valueNew =
static_cast<char>(std::tolower(
static_cast<int32_t
>(valueNew)));
214 connection->data.responseHeaders.emplace(key, value);
217 if (connection->data.responseHeaders.contains(
"content-length")) {
218 connection->data.contentLength = jsonifier::strToUint64(connection->data.responseHeaders.at(
"content-length").data());
220 connection->data.contentLength = std::numeric_limits<uint32_t>::max();
221 connection->data.currentState = https_state::Collecting_Chunked_Contents;
223 connection->data.isItChunked =
false;
224 if (connection->data.responseHeaders.contains(
"transfer-encoding")) {
225 if (connection->data.responseHeaders.at(
"transfer-encoding").find(
"chunked") != jsonifier::string_view::npos) {
226 connection->data.isItChunked =
true;
227 connection->data.contentLength = 0;
228 connection->data.currentState = https_state::Collecting_Chunked_Contents;
231 connection->data.responseCode = parseCodeNew;
232 if (connection->data.responseCode == 302) {
233 connection->workload.baseUrl = connection->data.responseHeaders.at(
"location");
234 connection->disconnect();
237 if (connection->data.responseCode != 200 && connection->data.responseCode != 201 && connection->data.responseCode != std::numeric_limits<uint32_t>::max()) {
238 connection->inputBufferReal.erase(connection->inputBufferReal.begin() +
static_cast<int64_t
>(stringViewNew.find(
"\r\n\r\n")) + 4);
239 connection->data.currentState = https_state::complete;
241 }
else if (!connection->data.isItChunked) {
242 connection->data.currentState = https_state::Collecting_Contents;
243 connection->inputBufferReal.erase(connection->inputBufferReal.begin() +
static_cast<int64_t
>(stringViewNew.find(
"\r\n\r\n")) + 4);
246 connection->inputBufferReal.erase(connection->inputBufferReal.begin() +
static_cast<int64_t
>(stringViewNew.find(
"\r\n\r\n")) + 4);
256 bool https_rnr_builder::parseChunk() {
257 auto connection{
static_cast<https_connection*
>(
this) };
258 jsonifier::string_view stringViewNew01{ connection->inputBufferReal };
259 if (
auto finalPosition = stringViewNew01.find(
"\r\n0\r\n\r\n"); finalPosition != jsonifier::string_view::npos) {
261 while (pos < stringViewNew01.size()) {
262 uint64_t lineEnd = stringViewNew01.find(
"\r\n", pos);
263 if (lineEnd == jsonifier::string_view::npos) {
267 jsonifier::string_view sizeLine{ stringViewNew01.data() + pos, lineEnd - pos };
268 uint64_t chunkSize = jsonifier::strToUint64<16>(
static_cast<jsonifier::string
>(sizeLine));
270 if (chunkSize == 0) {
276 jsonifier::string_view newString{ stringViewNew01.data() + pos, chunkSize };
277 connection->data.responseData += newString;
278 pos += chunkSize + 2;
280 connection->data.currentState = https_state::complete;
286 bool https_rnr_builder::parseContents() {
287 auto connection{
static_cast<https_connection*
>(
this) };
288 if (connection->inputBufferReal.size() >= connection->data.contentLength || !connection->data.contentLength) {
289 connection->data.responseData += jsonifier::string_view{ connection->inputBufferReal.data(), connection->data.contentLength };
290 connection->data.currentState = https_state::complete;
297 bool https_connection::areWeConnected() {
298 return tcp_connection::areWeStillConnected();
301 void https_connection::disconnect() {
302 tcp_connection::disconnect();
303 tcp_connection::reset();
306 void https_connection::resetValues(https_workload_data&& workloadDataNew, rate_limit_data* rateLimitDataNew) {
307 currentRateLimitData = rateLimitDataNew;
308 if (currentBaseUrl != workloadDataNew.baseUrl) {
309 tcp_connection::reset();
310 currentBaseUrl = workloadDataNew.baseUrl;
312 workload = std::move(workloadDataNew);
313 if (workload.baseUrl ==
"") {
314 workload.baseUrl =
"https://discord.com/api/v10";
316 inputBufferReal.clear();
317 data = https_response_data{};
320 https_connection_manager::https_connection_manager(rate_limit_queue* rateLimitDataQueueNew) {
321 rateLimitQueue = rateLimitDataQueueNew;
324 rate_limit_queue& https_connection_manager::getRateLimitQueue() {
325 return *rateLimitQueue;
328 https_connection& https_connection_manager::getConnection(https_workload_type workloadType) {
329 std::unique_lock lock{ accessMutex };
330 if (!httpsConnections.contains(workloadType)) {
331 httpsConnections.emplace(workloadType, makeUnique<https_connection>());
333 httpsConnections.at(workloadType)->currentReconnectTries = 0;
334 return *httpsConnections.at(workloadType).get();
337 https_connection_stack_holder::https_connection_stack_holder(https_connection_manager& connectionManager, https_workload_data&& workload) {
338 connection = &connectionManager.getConnection(workload.getWorkloadType());
339 rateLimitQueue = &connectionManager.getRateLimitQueue();
340 auto rateLimitData = connectionManager.getRateLimitQueue().getEndpointAccess(workload.getWorkloadType());
341 if (!rateLimitData) {
342 throw dca_exception{
"Failed to gain endpoint access." };
344 connection->resetValues(std::move(workload), rateLimitData);
345 if (!connection->areWeConnected()) {
346 *
static_cast<tcp_connection<https_connection>*
>(connection) = https_connection{ connection->workload.baseUrl,
static_cast<uint16_t
>(443) };
350 https_connection_stack_holder::~https_connection_stack_holder() {
351 rateLimitQueue->releaseEndPointAccess(connection->workload.getWorkloadType());
354 https_connection& https_connection_stack_holder::getConnection() {
358 https_client::https_client(jsonifier::string_view botTokenNew) : https_client_core(botTokenNew), connectionManager(&rateLimitQueue) {
359 rateLimitQueue.initialize();
362 https_response_data https_client::httpsRequest(https_connection& connection) {
363 https_response_data resultData = executeByRateLimitData(connection);
367 https_response_data https_client_core::httpsRequestInternal(https_connection& connection) {
368 if (connection.workload.baseUrl ==
"https://discord.com/api/v10") {
369 connection.workload.headersToInsert.emplace(
"Authorization",
"Bot " + botToken);
370 connection.workload.headersToInsert.emplace(
"User-Agent",
"DiscordCoreAPI (https://discordcoreapi.com/1.0)");
371 if (connection.workload.payloadType == payload_type::Application_Json) {
372 connection.workload.headersToInsert.emplace(
"Content-Type",
"application/json");
373 }
else if (connection.workload.payloadType == payload_type::Multipart_Form) {
374 connection.workload.headersToInsert.emplace(
"Content-Type",
"multipart/form-data; boundary=boundary25");
377 if (connection.currentReconnectTries >= connection.maxReconnectTries) {
378 connection.disconnect();
379 return https_response_data{};
381 if (!connection.areWeConnected()) {
382 connection.currentBaseUrl = connection.workload.baseUrl;
383 *
static_cast<tcp_connection<https_connection>*
>(&connection) = https_connection{ connection.workload.baseUrl,
static_cast<uint16_t
>(443) };
384 if (connection.currentStatus != connection_status::NO_Error || !connection.areWeConnected()) {
385 ++connection.currentReconnectTries;
386 connection.disconnect();
387 return httpsRequestInternal(connection);
390 auto request = connection.buildRequest(connection.workload);
391 if (connection.areWeConnected()) {
392 connection.writeData(
static_cast<jsonifier::string_view
>(request),
true);
393 if (connection.currentStatus != connection_status::NO_Error || !connection.areWeConnected()) {
394 ++connection.currentReconnectTries;
395 connection.disconnect();
396 return httpsRequestInternal(connection);
398 auto result = getResponse(connection);
399 if (
static_cast<int64_t
>(result.responseCode) == -1 || !connection.areWeConnected()) {
400 ++connection.currentReconnectTries;
401 connection.disconnect();
402 return httpsRequestInternal(connection);
407 ++connection.currentReconnectTries;
408 connection.disconnect();
409 return httpsRequestInternal(connection);
413 https_response_data https_client::executeByRateLimitData(https_connection& connection) {
414 https_response_data returnData{};
415 milliseconds timeRemaining{};
416 milliseconds currentTime = std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch());
417 if (connection.workload.workloadType == https_workload_type::Delete_Message_Old) {
418 connection.currentRateLimitData->sRemain.store(seconds{ 4 }, std::memory_order_release);
420 if (connection.workload.workloadType == https_workload_type::Post_Message || connection.workload.workloadType == https_workload_type::Patch_Message) {
421 connection.currentRateLimitData->areWeASpecialBucket.store(
true, std::memory_order_release);
423 if (connection.currentRateLimitData->areWeASpecialBucket.load(std::memory_order_acquire)) {
424 connection.currentRateLimitData->sRemain.store(seconds{
static_cast<int64_t
>(ceil(4.0f / 4.0f)) }, std::memory_order_release);
425 milliseconds targetTime{ connection.currentRateLimitData->sampledTimeInMs.load(std::memory_order_acquire) +
426 connection.currentRateLimitData->sRemain.load(std::memory_order_acquire) };
427 timeRemaining = targetTime - currentTime;
428 }
else if (connection.currentRateLimitData->doWeWait.load(std::memory_order_acquire)) {
429 milliseconds targetTime{ connection.currentRateLimitData->sampledTimeInMs.load(std::memory_order_acquire) +
430 connection.currentRateLimitData->sRemain.load(std::memory_order_acquire) };
431 timeRemaining = targetTime - currentTime;
432 connection.currentRateLimitData->doWeWait.store(
false, std::memory_order_release);
434 if (timeRemaining.count() > 0) {
435 message_printer::printSuccess<print_message_type::https>(
"we're waiting on rate-limit: " + jsonifier::toString(timeRemaining.count()));
436 milliseconds targetTime{ currentTime + timeRemaining };
437 while (targetTime > currentTime && targetTime.count() > 0 && currentTime.count() > 0 && timeRemaining.count() > 0) {
438 currentTime = std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch());
439 timeRemaining = targetTime - currentTime;
440 if (timeRemaining.count() <= 20) {
443 std::this_thread::sleep_for(milliseconds{
static_cast<int64_t
>(
static_cast<double>(timeRemaining.count()) * 80.0f / 100.0f) });
447 returnData = https_client::httpsRequestInternal(connection);
448 connection.currentRateLimitData->sampledTimeInMs.store(std::chrono::duration_cast<std::chrono::duration<int64_t, std::milli>>(sys_clock::now().time_since_epoch()),
449 std::memory_order_release);
451 if (returnData.responseCode == 204 || returnData.responseCode == 201 || returnData.responseCode == 200) {
452 message_printer::printSuccess<print_message_type::https>(
453 connection.workload.callStack +
" success: " +
static_cast<jsonifier::string
>(returnData.responseCode) +
": " + returnData.responseData);
454 }
else if (returnData.responseCode == 429) {
455 if (connection.data.responseHeaders.contains(
"x-ratelimit-retry-after")) {
456 connection.currentRateLimitData->sRemain.store(seconds{ jsonifier::strToInt64(connection.data.responseHeaders.at(
"x-ratelimit-retry-after").data()) / 1000LL },
457 std::memory_order_release);
459 connection.currentRateLimitData->doWeWait.store(
true, std::memory_order_release);
460 connection.currentRateLimitData->sampledTimeInMs.store(std::chrono::duration_cast<milliseconds>(sys_clock::now().time_since_epoch()), std::memory_order_release);
461 message_printer::printError<print_message_type::https>(connection.workload.callStack +
"::httpsRequest(), we've hit rate limit! time remaining: " +
462 jsonifier::toString(connection.currentRateLimitData->sRemain.load(std::memory_order_acquire).count()));
463 connection.resetValues(std::move(connection.workload), connection.currentRateLimitData);
464 returnData = executeByRateLimitData(connection);
469 https_response_data https_client_core::recoverFromError(https_connection& connection) {
470 if (connection.currentReconnectTries >= connection.maxReconnectTries) {
471 connection.disconnect();
472 return connection.finalizeReturnValues(*connection.currentRateLimitData);
474 ++connection.currentReconnectTries;
475 connection.disconnect();
476 std::this_thread::sleep_for(150ms);
477 return httpsRequestInternal(connection);
480 https_response_data https_client_core::getResponse(https_connection& connection) {
481 stop_watch<milliseconds> stopWatch{ 10000ms };
483 while (connection.data.currentState != https_state::complete && !stopWatch.hasTimeElapsed()) {
484 if (connection.areWeConnected()) {
485 auto newState = connection.processIO(10);
487 case connection_status::NO_Error: {
490 case connection_status::CONNECTION_Error:
492 case connection_status::POLLERR_Error:
494 case connection_status::POLLHUP_Error:
496 case connection_status::POLLNVAL_Error:
498 case connection_status::READ_Error:
500 case connection_status::WRITE_Error:
502 case connection_status::SOCKET_Error:
505 return recoverFromError(connection);
509 return recoverFromError(connection);
512 return connection.finalizeReturnValues(*connection.currentRateLimitData);
The main namespace for the forward-facing interfaces.