From 524a462446bb48175d4ca1d32634d76fb16b86d9 Mon Sep 17 00:00:00 2001
From: SONY-STRING <sctoa1@gmail.com>
Date: Wed, 3 Jul 2024 17:22:11 +0900
Subject: [PATCH 1/2] feat: add request_timeout parameter as optional parameter
 to request and co_request

---
 buildtools/classes/Generator/CoroGenerator.php |  4 ++--
 include/dpp/cluster.h                          |  3 ++-
 include/dpp/cluster_coro_calls.h               |  2 +-
 include/dpp/queues.h                           |  8 +++++++-
 src/dpp/cluster.cpp                            |  4 ++--
 src/dpp/cluster_coro_calls.cpp                 |  4 ++--
 src/dpp/queues.cpp                             | 10 +++++-----
 7 files changed, 21 insertions(+), 14 deletions(-)

diff --git a/buildtools/classes/Generator/CoroGenerator.php b/buildtools/classes/Generator/CoroGenerator.php
index 1a68809a70..7749054bc9 100644
--- a/buildtools/classes/Generator/CoroGenerator.php
+++ b/buildtools/classes/Generator/CoroGenerator.php
@@ -116,7 +116,7 @@ public function getCommentArray(): array
      */
     public function saveHeader(string $content): void
     {
-        $content .= "[[nodiscard]] async<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap<std::string, std::string> &headers = {}, const std::string &protocol = \"1.1\");\n\n";
+        $content .= "[[nodiscard]] async<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = \"\", const std::string &mimetype = \"text/plain\", const std::multimap<std::string, std::string> &headers = {}, const std::string &protocol = \"1.1\", time_t request_timeout = 5);\n\n";
         file_put_contents('include/dpp/cluster_coro_calls.h', $content);
     }
 
@@ -125,7 +125,7 @@ public function saveHeader(string $content): void
      */
     public function saveCpp(string $cppcontent): void
     {
-        $cppcontent .= "dpp::async<dpp::http_request_completion_t> dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap<std::string, std::string> &headers, const std::string &protocol) {\n\treturn async<http_request_completion_t>{ [&, this] <typename C> (C &&cc) { return this->request(url, method, std::forward<C>(cc), postdata, mimetype, headers, protocol); }};\n}
+        $cppcontent .= "dpp::async<dpp::http_request_completion_t> dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap<std::string, std::string> &headers, const std::string &protocol, time_t request_timeout) {\n\treturn async<http_request_completion_t>{ [&, this] <typename C> (C &&cc) { return this->request(url, method, std::forward<C>(cc), postdata, mimetype, headers, protocol, request_timeout); }};\n}
 
 #endif
 ";
diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h
index 9b486bbd53..48d5203492 100644
--- a/include/dpp/cluster.h
+++ b/include/dpp/cluster.h
@@ -1400,8 +1400,9 @@ class DPP_EXPORT cluster {
 	 * @param mimetype MIME type of POST data
 	 * @param headers Headers to send with the request
 	 * @param protocol HTTP protocol to use (1.1 and 1.0 are supported)
+	 * @param request_timeout How many seconds before the connection is considered failed if not finished
 	 */
-	void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap<std::string, std::string> &headers = {}, const std::string &protocol = "1.1");
+	void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap<std::string, std::string> &headers = {}, const std::string &protocol = "1.1", time_t request_timeout = 5);
 
 	/**
 	 * @brief Respond to a slash command
diff --git a/include/dpp/cluster_coro_calls.h b/include/dpp/cluster_coro_calls.h
index f46efaf581..831537f9f4 100644
--- a/include/dpp/cluster_coro_calls.h
+++ b/include/dpp/cluster_coro_calls.h
@@ -2566,5 +2566,5 @@
 
 
 /* End of auto-generated definitions */
-[[nodiscard]] async<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap<std::string, std::string> &headers = {}, const std::string &protocol = "1.1");
+[[nodiscard]] async<http_request_completion_t> co_request(const std::string &url, http_method method, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap<std::string, std::string> &headers = {}, const std::string &protocol = "1.1", time_t request_timeout = 5);
 
diff --git a/include/dpp/queues.h b/include/dpp/queues.h
index 3097d5bcb6..1ae28b5400 100644
--- a/include/dpp/queues.h
+++ b/include/dpp/queues.h
@@ -293,6 +293,11 @@ class DPP_EXPORT http_request {
 	 */
 	std::string protocol;
 
+	/**
+	 * @brief How many seconds before the connection is considered failed if not finished
+	 */
+	time_t request_timeout;
+
 	/**
 	 * @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request().
 	 * @param _endpoint The API endpoint, e.g. /api/guilds
@@ -332,8 +337,9 @@ class DPP_EXPORT http_request {
 	 * @param _mimetype POST data mime type
 	 * @param _headers HTTP headers to send
 	 * @param http_protocol HTTP protocol
+	 * @param _request_timeout How many seconds before the connection is considered failed if not finished
 	 */
-	http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap<std::string, std::string> &_headers = {}, const std::string &http_protocol = "1.1");
+	http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap<std::string, std::string> &_headers = {}, const std::string &http_protocol = "1.1", time_t _request_timeout = 5);
 
 	/**
 	 * @brief Destroy the http request object
diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp
index db8ce14c31..395d916804 100644
--- a/src/dpp/cluster.cpp
+++ b/src/dpp/cluster.cpp
@@ -366,8 +366,8 @@ void cluster::post_rest_multipart(const std::string &endpoint, const std::string
 }
 
 
-void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap<std::string, std::string> &headers, const std::string &protocol) {
-	raw_rest->post_request(std::make_unique<http_request>(url, callback, method, postdata, mimetype, headers, protocol));
+void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap<std::string, std::string> &headers, const std::string &protocol, time_t request_timeout) {
+	raw_rest->post_request(std::make_unique<http_request>(url, callback, method, postdata, mimetype, headers, protocol, request_timeout));
 }
 
 gateway::gateway() : shards(0), session_start_total(0), session_start_remaining(0), session_start_reset_after(0), session_start_max_concurrency(0) {
diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp
index 27e274a81e..52bf1e814e 100644
--- a/src/dpp/cluster_coro_calls.cpp
+++ b/src/dpp/cluster_coro_calls.cpp
@@ -851,8 +851,8 @@ async<confirmation_callback_t> cluster::co_get_webhook_with_token(snowflake webh
 };
 
 /* End of auto-generated definitions */
-dpp::async<dpp::http_request_completion_t> dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap<std::string, std::string> &headers, const std::string &protocol) {
-	return async<http_request_completion_t>{ [&, this] <typename C> (C &&cc) { return this->request(url, method, std::forward<C>(cc), postdata, mimetype, headers, protocol); }};
+dpp::async<dpp::http_request_completion_t> dpp::cluster::co_request(const std::string &url, http_method method, const std::string &postdata, const std::string &mimetype, const std::multimap<std::string, std::string> &headers, const std::string &protocol, time_t request_timeout) {
+	return async<http_request_completion_t>{ [&, this] <typename C> (C &&cc) { return this->request(url, method, std::forward<C>(cc), postdata, mimetype, headers, protocol, request_timeout); }};
 }
 
 #endif
diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp
index 3625fadd2b..b179edc747 100644
--- a/src/dpp/queues.cpp
+++ b/src/dpp/queues.cpp
@@ -33,7 +33,7 @@
 namespace dpp {
 
 http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method _method, const std::string &audit_reason, const std::string &filename, const std::string &filecontent, const std::string &filemimetype, const std::string &http_protocol)
- : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata),  method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol)
+ : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata),  method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol), request_timeout(5)
 {
 	if (!filename.empty()) {
 		file_name.push_back(filename);
@@ -47,13 +47,13 @@ http_request::http_request(const std::string &_endpoint, const std::string &_par
 }
 
 http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method method, const std::string &audit_reason, const std::vector<std::string> &filename, const std::vector<std::string> &filecontent, const std::vector<std::string> &filemimetypes, const std::string &http_protocol)
- : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata),  method(method), reason(audit_reason), file_name(filename), file_content(filecontent), file_mimetypes(filemimetypes), mimetype("application/json"), waiting(false), protocol(http_protocol)
+ : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata),  method(method), reason(audit_reason), file_name(filename), file_content(filecontent), file_mimetypes(filemimetypes), mimetype("application/json"), waiting(false), protocol(http_protocol), request_timeout(5)
 {
 }
 
 
-http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap<std::string, std::string> &_headers, const std::string &http_protocol)
- : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false), protocol(http_protocol)
+http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap<std::string, std::string> &_headers, const std::string &http_protocol, time_t _request_timeout)
+ : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false), protocol(http_protocol), request_timeout(_request_timeout)
 {
 }
 
@@ -176,7 +176,7 @@ http_request_completion_t http_request::run(cluster* owner) {
 	}
 	http_connect_info hci = https_client::get_host_info(_host);
 	try {
-		https_client cli(hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, 5, protocol);
+		https_client cli(hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, request_timeout, protocol);
 		rv.latency = dpp::utility::time_f() - start;
 		if (cli.get_status() < 100) {
 			rv.error = h_connection;

From 17a564ab81094117db24fb340f9b167973c67df2 Mon Sep 17 00:00:00 2001
From: SONY-STRING <sctoa1@gmail.com>
Date: Wed, 3 Jul 2024 19:34:13 +0900
Subject: [PATCH 2/2] fix: add error message for request timeouts and add
 timed_out member variable to https_client to check for request timeouts

---
 include/dpp/httpsclient.h | 5 +++++
 src/dpp/httpsclient.cpp   | 7 ++++++-
 src/dpp/queues.cpp        | 5 ++++-
 3 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h
index 7884baab39..0090c68af4 100644
--- a/include/dpp/httpsclient.h
+++ b/include/dpp/httpsclient.h
@@ -230,6 +230,11 @@ class DPP_EXPORT https_client : public ssl_client {
 	http_state get_state();
 
 public:
+	/**
+	 * @brief If true the response timed out while waiting
+	 */
+	bool timed_out;	
+	
 	/**
 	 * @brief Connect to a specific HTTP(S) server and complete a request.
 	 * 
diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp
index b18675cd65..29598cd375 100644
--- a/src/dpp/httpsclient.cpp
+++ b/src/dpp/httpsclient.cpp
@@ -42,7 +42,8 @@ https_client::https_client(const std::string &hostname, uint16_t port,  const st
 	request_headers(extra_headers),
 	status(0),
 	http_protocol(protocol),
-	timeout(request_timeout)
+	timeout(request_timeout),
+	timed_out(false)
 {
 	nonblocking = false;
 	timeout = time(nullptr) + request_timeout;
@@ -313,6 +314,10 @@ http_state https_client::get_state() {
 
 void https_client::one_second_timer() {
 	if ((this->sfd == SOCKET_ERROR || time(nullptr) >= timeout) && this->state != HTTPS_DONE) {
+		/* if and only if response is timed out */
+		if (this->sfd != SOCKET_ERROR) {
+			timed_out = true;
+		}
 		keepalive = false;
 		this->close();
 	}
diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp
index b179edc747..b6d691210a 100644
--- a/src/dpp/queues.cpp
+++ b/src/dpp/queues.cpp
@@ -178,7 +178,10 @@ http_request_completion_t http_request::run(cluster* owner) {
 	try {
 		https_client cli(hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, request_timeout, protocol);
 		rv.latency = dpp::utility::time_f() - start;
-		if (cli.get_status() < 100) {
+		if (cli.timed_out) {
+			rv.error = h_connection;			
+			owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": Timed out while waiting for the response");
+		} else if (cli.get_status() < 100) {
 			rv.error = h_connection;
 			owner->log(ll_error, "HTTP(S) error on " + hci.scheme + " connection to " + hci.hostname + ":" + std::to_string(hci.port) + ": Malformed HTTP response");
 		} else {