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 responseData{};
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 std::atomic_bool areWeCheckedOut{};
112 int32_t currentReconnectTries{};
113 StringBuffer inputBufferReal{};
114 std::string currentBaseUrl{};
115 Jsonifier::Parser parser{};
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 +
173 " Https Error: " + static_cast<std::string>(returnData.responseCode) + "\nThe Request: " + workload.content +
174 DiscordCoreAPI::reset() + "" };
175 theError.errorCode = returnData.responseCode;
176 httpsConnection->areWeCheckedOut.store(false);
177 throw theError;
178 }
179 if (returnData.responseData.size() > 0) {
180 Jsonifier::Value document{};
181 returnData.responseData.reserve(returnData.responseData.size() + 256);
182 if (httpsConnection->parser.parseJson(returnData.responseData).get(document) == Jsonifier::Success) {
183 if (returnValue) {
184 *returnValue = RTy{ document };
185 httpsConnection->areWeCheckedOut.store(false);
186 return *returnValue;
187 } else {
188 httpsConnection->areWeCheckedOut.store(false);
189 return RTy{ document };
190 }
191 }
192 }
193 httpsConnection->areWeCheckedOut.store(false);
194 return RTy{};
195 }
196
197 template<SameAsVoid RTy> RTy submitWorkloadAndGetResult(const HttpsWorkloadData& workload, RTy* returnValue = nullptr);
198
199 HttpsResponseData submitWorkloadAndGetResult(const HttpsWorkloadData& workloadNew);
200
201 HttpsResponseData httpsRequest(HttpsConnection* httpsConnection, const HttpsWorkloadData& workload);
202
203 protected:
204 DiscordCoreAPI::ConfigManager* configManager{ nullptr };
205 HttpsConnectionManager connectionManager{ nullptr };
206
207 HttpsResponseData httpsRequestInternal(HttpsConnection* connection, const HttpsWorkloadData& workload,
208 RateLimitData& rateLimitData);
209
210 HttpsResponseData executeByRateLimitData(HttpsConnection* httpsConnection, const HttpsWorkloadData& workload,
211 RateLimitData& rateLimitData);
212
213 HttpsResponseData getResponse(HttpsConnection* connection, RateLimitData& rateLimitData);
214 };
215
216}// namespace DiscordCoreInternal