DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
Https.hpp
Go to the documentation of this file.
1/*
2 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
3
4 Copyright 2021, 2022 Chris M. (RealTimeChris)
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19 USA
20*/
21/// Https.hpp - Header file for the "Https Stuff".
22/// May 12, 2021
23/// https://discordcoreapi.com
24/// \file Https.hpp
25
26#pragma once
27
29#include <semaphore>
30
31namespace DiscordCoreInternal {
32
33 class DiscordCoreAPI_Dll HttpsConnectionManager;
34 struct DiscordCoreAPI_Dll RateLimitData;
35
36 enum class HttpsState { Collecting_Code = 0, Collecting_Headers = 1, Collecting_Size = 2, Collecting_Contents = 3 };
37
38 class DiscordCoreAPI_Dll HttpsError : public DiscordCoreAPI::DCAException {
39 public:
40 int32_t errorCode{};
41 explicit HttpsError(std::string message);
42 };
43
44 struct DiscordCoreAPI_Dll HttpsResponseData {
45 friend class HttpsRnRBuilder;
46 friend class HttpsConnection;
47 friend class HttpsClient;
48
49 std::unordered_map<std::string, std::string> responseHeaders{};
50 HttpsResponseCode responseCode{ static_cast<uint32_t>(-1) };
51 HttpsState currentState{ HttpsState::Collecting_Code };
52 std::string responseMessage{};
53 uint64_t contentLength{};
54
55 protected:
56 bool isItChunked{};
57 };
58
59 class DiscordCoreAPI_Dll HttpsRnRBuilder {
60 public:
61 friend class HttpsClient;
62
63 HttpsRnRBuilder(bool doWePrintErrorMessages);
64
65 void updateRateLimitData(RateLimitData& connection, std::unordered_map<std::string, std::string>& headers);
66
67 HttpsResponseData finalizeReturnValues(RateLimitData& rateLimitData);
68
69 std::string buildRequest(const HttpsWorkloadData& workload);
70
71 uint64_t parseHeaders(StringBuffer& other);
72
73 bool parseChunk(StringBuffer& other);
74
75 virtual ~HttpsRnRBuilder() noexcept = default;
76
77 protected:
78 bool doWePrintErrorMessages{};
79 bool doWeHaveContentSize{};
80 bool doWeHaveHeaders{};
81 bool isItChunked{};
82
83 uint64_t parseSize(StringBuffer& other);
84
85 uint64_t parseCode(StringBuffer& other);
86
87 void clearCRLF(StringBuffer& other);
88 };
89
90 struct DiscordCoreAPI_Dll RateLimitData {
91 friend class HttpsConnectionManager;
92 friend class HttpsRnRBuilder;
93 friend class HttpsClient;
94
95 protected:
96 std::atomic<Milliseconds> sampledTimeInMs{ Milliseconds{} };
97 std::atomic<Milliseconds> msRemain{ Milliseconds{} };
98 std::counting_semaphore<1> theSemaphore{ 1 };
99 std::atomic_bool areWeASpecialBucket{};
100 std::atomic_bool didWeHitRateLimit{};
101 std::atomic_int64_t getsRemaining{};
102 std::atomic_bool haveWeGoneYet{};
103 std::atomic_bool doWeWait{};
104 std::string tempBucket{};
105 std::string bucket{};
106 };
107
108 class DiscordCoreAPI_Dll HttpsConnection : public TCPSSLClient, public HttpsRnRBuilder {
109 public:
110 const int32_t maxReconnectTries{ 10 };
111 simdjson::ondemand::parser parser{};
112 std::atomic_bool areWeCheckedOut{};
113 int32_t currentReconnectTries{};
114 StringBuffer inputBufferReal{};
115 std::string currentBaseUrl{};
116 bool areWeDoneTheRequest{};
117 HttpsResponseData data{};
118 bool doWeConnect{ true };
119
120 HttpsConnection(bool doWePrintErrorMessages);
121
122 void handleBuffer() noexcept;
123
124 void disconnect() noexcept;
125
126 void resetValues();
127
128 virtual ~HttpsConnection() noexcept = default;
129 };
130
131 class DiscordCoreAPI_Dll HttpsConnectionManager {
132 public:
133 HttpsConnectionManager(DiscordCoreAPI::ConfigManager*);
134
135 std::unordered_map<std::string, std::unique_ptr<RateLimitData>>& getRateLimitValues();
136
137 std::unordered_map<HttpsWorkloadType, std::string>& getRateLimitValueBuckets();
138
139 HttpsConnection* getConnection();
140
141 void initialize();
142
143 protected:
144 std::unordered_map<std::string, std::unique_ptr<RateLimitData>> rateLimitValues{};
145 std::unordered_map<int64_t, std::unique_ptr<HttpsConnection>> httpsConnections{};
146 std::unordered_map<HttpsWorkloadType, std::string> rateLimitValueBuckets{};
147 DiscordCoreAPI::ConfigManager* configManager{ nullptr };
148 std::mutex accessMutex{};
149 int64_t currentIndex{};
150 };
151
152 template<typename OTy>
153 concept SameAsVoid = std::same_as<void, OTy>;
154
155 class DiscordCoreAPI_Dll HttpsClient {
156 public:
157 HttpsClient(DiscordCoreAPI::ConfigManager* configManager);
158
159 template<typename RTy> RTy submitWorkloadAndGetResult(const HttpsWorkloadData& workload, RTy* returnValue) {
160 workload.headersToInsert["Authorization"] = "Bot " + this->configManager->getBotToken();
161 workload.headersToInsert["User-Agent"] = "DiscordBot (https://discordcoreapi.com/ 1.0)";
162 if (workload.payloadType == PayloadType::Application_Json) {
163 workload.headersToInsert["Content-Type"] = "application/json";
164 } else if (workload.payloadType == PayloadType::Multipart_Form) {
165 workload.headersToInsert["Content-Type"] = "multipart/form-data; boundary=boundary25";
166 }
167 auto httpsConnection = this->connectionManager.getConnection();
168 HttpsResponseData returnData = this->httpsRequest(*httpsConnection, workload);
169
170 if (static_cast<uint32_t>(returnData.responseCode) != 200 && static_cast<uint32_t>(returnData.responseCode) != 204 &&
171 static_cast<uint32_t>(returnData.responseCode) != 201) {
172 HttpsError theError{ DiscordCoreAPI::shiftToBrightRed() + workload.callStack + " Https Error: " +
173 static_cast<std::string>(returnData.responseCode) + "\nThe Request: " + workload.content + DiscordCoreAPI::reset() + "\n\n" };
174 theError.errorCode = returnData.responseCode;
175 httpsConnection->areWeCheckedOut.store(false);
176 throw theError;
177 }
178
179 if (returnData.responseMessage.size() > 0 && returnData.responseMessage.size() >= returnData.contentLength) {
180 returnData.responseMessage.reserve(returnData.responseMessage.size() + simdjson::SIMDJSON_PADDING);
181 simdjson::ondemand::document document{};
182 if (httpsConnection->parser
183 .iterate(returnData.responseMessage.data(), returnData.responseMessage.length(), returnData.responseMessage.capacity())
184 .get(document) == simdjson::error_code::SUCCESS) {
185 if (document.type() != simdjson::ondemand::json_type::null) {
186 simdjson::ondemand::value object{};
187 if (document.get(object) == simdjson::error_code::SUCCESS) {
188 if (returnValue) {
189 auto returnValueNew = RTy{ object };
190 *returnValue = returnValueNew;
191 httpsConnection->areWeCheckedOut.store(false);
192 return *returnValue;
193 } else {
194 RTy returnValueNew{ object };
195 httpsConnection->areWeCheckedOut.store(false);
196 return returnValueNew;
197 }
198 }
199 }
200 }
201 }
202 RTy returnValueNew{};
203 httpsConnection->areWeCheckedOut.store(false);
204 return returnValueNew;
205 }
206
207 template<SameAsVoid RTy> RTy submitWorkloadAndGetResult(const HttpsWorkloadData& workload, RTy* returnValue = nullptr);
208
209 HttpsResponseData submitWorkloadAndGetResult(const HttpsWorkloadData& workloadNew);
210
211 HttpsResponseData httpsRequest(HttpsConnection& httpsConnection, const HttpsWorkloadData& workload);
212
213 protected:
214 DiscordCoreAPI::ConfigManager* configManager{ nullptr };
215 HttpsConnectionManager connectionManager{ nullptr };
216
217 HttpsResponseData httpsRequestInternal(HttpsConnection& connection, const HttpsWorkloadData& workload, RateLimitData& rateLimitData);
218
219 HttpsResponseData executeByRateLimitData(HttpsConnection& httpsConnection, const HttpsWorkloadData& workload, RateLimitData& rateLimitData);
220
221 HttpsResponseData getResponse(HttpsConnection& connection, RateLimitData& rateLimitData);
222 };
223
224}// namespace DiscordCoreInternal