DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
HttpsClient.hpp
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.hpp - Header file for the "Https stuff".
27/// May 12, 2021
28/// https://discordcoreapi.com
29/// \file HttpsClient.hpp
30#pragma once
31
33
34namespace discord_core_api {
35
36 namespace discord_core_internal {
37
38 /// \brief Voice websocket close codes.
40 public:
41 /// \brief Voice websocket close codes.
42 enum class https_response_codes : uint64_t {
43 unset = std::numeric_limits<uint64_t>::max(),
44 ok = 200,///< The request completed successfully.
45 created = 201,///< The entity was created successfully.
46 No_Content = 204,///< The request completed successfully but returned no content.
47 Not_Modifies = 304,///< The entity was not modified (no action was taken).
48 Bad_Request = 400,///< The request was improperly formatted, or the server couldn't understand it.
49 unauthorized = 401,///< The authorization header was missing or invalid.
50 forbidden = 403,///< The authorization token you passed did not have permission to the resource.
51 Not_Found = 404,///< The resource at the location specified doesn't exist.
52 Method_Not_Allowed = 405,///< The https method used is not valid for the location specified.
53 Too_Many_Requests = 429,///< You are being rate limited, see rate limits.
54 Gateway_Unavailable = 502,///< There was not a gateway available to process your request. wait a bit and retry.
55 };
56
57 inline static unordered_map<https_response_codes, jsonifier::string_view> outputErrorValues{
58 { static_cast<https_response_codes>(200), "the request completed successfully" }, { static_cast<https_response_codes>(201), "the entity was created successfully" },
59 { static_cast<https_response_codes>(204), "the request completed successfully but returned no content" },
60 { static_cast<https_response_codes>(304), "the entity was not modified (no action was taken)" },
61 { static_cast<https_response_codes>(400), "the request was improperly formatted, or the server couldn't understand it" },
62 { static_cast<https_response_codes>(401), "the authorization header was missing or invalid" },
63 { static_cast<https_response_codes>(403), "the authorization token you passed did not have permission to the resource" },
64 { static_cast<https_response_codes>(404), "the resource at the location specified doesn't exist" },
65 { static_cast<https_response_codes>(405), "the https method used is not valid for the location specified" },
66 { static_cast<https_response_codes>(429), "you are being rate limited, see rate limits" },
67 { static_cast<https_response_codes>(502), "there was not a gateway available to process your request.wait a bit and retry" },
68 { static_cast<https_response_codes>(500), "the server had an error processing your request(these are rare)" }
69 };
70
72
73 inline https_response_code() = default;
74
75 inline https_response_code& operator=(uint64_t valueNew) {
76 value = static_cast<https_response_codes>(valueNew);
77 return *this;
78 }
79
80 inline https_response_code(uint64_t value) {
81 *this = value;
82 }
83
84 inline operator jsonifier::string() {
85 return jsonifier::string{ "code: " + jsonifier::toString(static_cast<uint32_t>(value)) + jsonifier::string{ ", message: " } +
86 static_cast<jsonifier::string>(https_response_code::outputErrorValues[value]) };
87 }
88
89 inline operator uint64_t() {
90 return static_cast<uint64_t>(value);
91 }
92 };
93
94 class https_connection_manager;
95 struct rate_limit_data;
96
97 enum class https_state { Collecting_Headers = 0, Collecting_Contents = 1, Collecting_Chunked_Contents = 2, complete = 3 };
98
99 class https_error : public dca_exception {
100 public:
101 https_response_code errorCode{};
102 inline https_error(jsonifier::string_view message, const std::source_location& location = std::source_location::current()) : dca_exception{ message, location } {};
103 };
104
105 struct DiscordCoreAPI_Dll https_response_data {
106 friend class https_rnr_builder;
107 friend class https_connection;
108 friend class https_client;
109
110 https_response_code responseCode{ std::numeric_limits<uint32_t>::max() };
111 unordered_map<jsonifier::string, jsonifier::string> responseHeaders{};
112 https_state currentState{ https_state::Collecting_Headers };
113 jsonifier::string responseData{};
114 uint64_t contentLength{};
115
116 protected:
117 bool isItChunked{};
118 };
119
120 class DiscordCoreAPI_Dll https_rnr_builder {
121 public:
122 friend class https_client;
123
124 https_rnr_builder() = default;
125
126 https_response_data finalizeReturnValues(rate_limit_data& rateLimitData);
127
128 jsonifier::string buildRequest(const https_workload_data& workload);
129
130 void updateRateLimitData(rate_limit_data& rateLimitData);
131
132 bool parseHeaders();
133
134 virtual ~https_rnr_builder() = default;
135
136 protected:
137 bool parseContents();
138
139 bool parseChunk();
140 };
141
142 class DiscordCoreAPI_Dll https_connection : public https_rnr_builder, public tcp_connection<https_connection> {
143 public:
144 template<typename value_type> friend class https_tcp_connection;
145
146 rate_limit_data* currentRateLimitData{};
147 const int32_t maxReconnectTries{ 3 };
148 jsonifier::string inputBufferReal{};
149 jsonifier::string currentBaseUrl{};
150 int32_t currentReconnectTries{};
151 https_workload_data workload{};
152 https_response_data data{};
153
154 https_connection() = default;
155
156 https_connection(const jsonifier::string& baseUrlNew, const uint16_t portNew);
157
158 void resetValues(https_workload_data&& workloadNew, rate_limit_data* newRateLimitData);
159
160 void handleBuffer() override;
161
162 bool areWeConnected();
163
164 void disconnect();
165
166 virtual ~https_connection() = default;
167 };
168
169 /// @class https_connection_manager.
170 /// @brief For managing the collection of Https connections.
171 class DiscordCoreAPI_Dll https_connection_manager {
172 public:
173 friend class https_client;
174
175 https_connection_manager() = default;
176
177 https_connection_manager(rate_limit_queue*);
178
179 https_connection& getConnection(https_workload_type workloadType);
180
181 rate_limit_queue& getRateLimitQueue();
182
183 protected:
184 unordered_map<https_workload_type, unique_ptr<https_connection>> httpsConnections{};///< Collection of Https connections.
185 rate_limit_queue* rateLimitQueue{};
186 std::mutex accessMutex{};
187 };
188
189 class DiscordCoreAPI_Dll https_connection_stack_holder {
190 public:
191 https_connection_stack_holder(https_connection_manager& connectionManager, https_workload_data&& workload);
192
193 https_connection& getConnection();
194
195 ~https_connection_stack_holder();
196
197 protected:
198 rate_limit_queue* rateLimitQueue{};
199 https_connection* connection{};
200 };
201
202 class DiscordCoreAPI_Dll https_client_core {
203 public:
204 https_client_core(jsonifier::string_view botTokenNew);
205
206 inline https_response_data submitWorkloadAndGetResult(https_workload_data&& workloadNew) {
207 https_connection connection{};
208 rate_limit_data rateLimitData{};
209 connection.resetValues(std::move(workloadNew), &rateLimitData);
210 auto returnData = httpsRequestInternal(connection);
211 if (returnData.responseCode != 200 && returnData.responseCode != 204 && returnData.responseCode != 201) {
212 jsonifier::string errorMessage{};
213 if (connection.workload.callStack != "") {
214 errorMessage += connection.workload.callStack + " ";
215 }
216 errorMessage += "Https error: " + returnData.responseCode.operator jsonifier::string() + "\nThe request: base url: " + connection.workload.baseUrl + "\n";
217 if (!connection.workload.relativePath.empty()) {
218 errorMessage += "Relative Url: " + connection.workload.relativePath + "\n";
219 }
220 if (!connection.workload.content.empty()) {
221 errorMessage += "Content: " + connection.workload.content + "\n";
222 }
223 if (!returnData.responseData.empty()) {
224 errorMessage += "The Response: " + static_cast<jsonifier::string>(returnData.responseData);
225 }
226 https_error theError{ errorMessage };
227 theError.errorCode = returnData.responseCode;
228 throw theError;
229 }
230 return returnData;
231 }
232
233 protected:
234 jsonifier::string botToken{};
235
236 https_response_data httpsRequestInternal(https_connection& connection);
237
238 https_response_data recoverFromError(https_connection& connection);
239
240 https_response_data getResponse(https_connection& connection);
241 };
242
243 /**
244 * \addtogroup discord_core_internal
245 * @{
246 */
247
248 /// @class https_client
249 /// @brief For sending Https requests.
250 class DiscordCoreAPI_Dll https_client : public https_client_core {
251 public:
252 https_client(jsonifier::string_view botTokenNew);
253
254 template<typename value_type, typename string_type> void getParseErrors(jsonifier::jsonifier_core& parser, value_type& value, string_type& stringNew) {
255 parser.parseJson<true, true>(value, stringNew);
256 if (auto result = parser.getErrors(); result.size() > 0) {
257 for (auto& valueNew: result) {
258 message_printer::printError<print_message_type::websocket>(valueNew.reportError());
259 }
260 }
261 }
262
263 template<typename workload_type, typename... args> void submitWorkloadAndGetResult(workload_type&& workload, args&... argsNew) {
264 https_connection_stack_holder stackHolder{ connectionManager, std::move(workload) };
265 https_response_data returnData = httpsRequest(stackHolder.getConnection());
266 if (static_cast<uint32_t>(returnData.responseCode) != 200 && static_cast<uint32_t>(returnData.responseCode) != 204 &&
267 static_cast<uint32_t>(returnData.responseCode) != 201) {
268 jsonifier::string errorMessage{};
269 if (stackHolder.getConnection().workload.callStack != "") {
270 errorMessage += stackHolder.getConnection().workload.callStack + " ";
271 }
272 errorMessage +=
273 "Https error: " + returnData.responseCode.operator jsonifier::string() + "\nThe request: base url: " + stackHolder.getConnection().workload.baseUrl + "\n";
274 if (!stackHolder.getConnection().workload.relativePath.empty()) {
275 errorMessage += "Relative Url: " + stackHolder.getConnection().workload.relativePath + "\n";
276 }
277 if (!stackHolder.getConnection().workload.content.empty()) {
278 errorMessage += "Content: " + stackHolder.getConnection().workload.content + "\n";
279 }
280 if (!returnData.responseData.empty()) {
281 errorMessage += "The Response: " + static_cast<jsonifier::string>(returnData.responseData);
282 }
283 https_error theError{ errorMessage };
284 theError.errorCode = returnData.responseCode;
285
286 throw theError;
287 }
288
289 if constexpr ((( !std::is_void_v<args> ) || ...)) {
290 if (returnData.responseData.size() > 0) {
291 (getParseErrors(parser, argsNew, returnData.responseData), ...);
292 }
293 }
294 }
295
296 protected:
297 https_connection_manager connectionManager{};
298 rate_limit_queue rateLimitQueue{};
299
300 https_response_data executeByRateLimitData(https_connection& connection);
301
302 https_response_data httpsRequest(https_connection& connection);
303 };
304
305 /**@}*/
306
307 }// namespace discord_core_internal
308}
For managing the collection of Https connections.
@ No_Content
The request completed successfully but returned no content.
@ Gateway_Unavailable
There was not a gateway available to process your request. wait a bit and retry.
@ forbidden
The authorization token you passed did not have permission to the resource.
@ Not_Modifies
The entity was not modified (no action was taken).
@ Not_Found
The resource at the location specified doesn't exist.
@ Bad_Request
The request was improperly formatted, or the server couldn't understand it.
@ Method_Not_Allowed
The https method used is not valid for the location specified.
The main namespace for the forward-facing interfaces.
dca_exception(jsonifier::string_view error, std::source_location location=std::source_location::current())
Constructor to create a dca_exception with an error message and optional source location.
Definition: Base.hpp:824