Преглед изворни кода

Adds proxy support for HTTPClient

Also fixed a potential infinite loop when connecting to server.
Haoyu Qiu пре 4 година
родитељ
комит
c09ea8d45a
5 измењених фајлова са 129 додато и 8 уклоњено
  1. 11 0
      core/io/http_client.cpp
  2. 4 0
      core/io/http_client.h
  3. 86 7
      core/io/http_client_tcp.cpp
  4. 10 1
      core/io/http_client_tcp.h
  5. 18 0
      doc/classes/HTTPClient.xml

+ 11 - 0
core/io/http_client.cpp

@@ -49,6 +49,14 @@ HTTPClient *HTTPClient::create() {
 	return nullptr;
 }
 
+void HTTPClient::set_http_proxy(const String &p_host, int p_port) {
+	WARN_PRINT("HTTP proxy feature is not available");
+}
+
+void HTTPClient::set_https_proxy(const String &p_host, int p_port) {
+	WARN_PRINT("HTTPS proxy feature is not available");
+}
+
 Error HTTPClient::_request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
 	int size = p_body.size();
 	return request(p_method, p_url, p_headers, size > 0 ? p_body.ptr() : nullptr, size);
@@ -142,6 +150,9 @@ void HTTPClient::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_status"), &HTTPClient::get_status);
 	ClassDB::bind_method(D_METHOD("poll"), &HTTPClient::poll);
 
+	ClassDB::bind_method(D_METHOD("set_http_proxy", "host", "port"), &HTTPClient::set_http_proxy);
+	ClassDB::bind_method(D_METHOD("set_https_proxy", "host", "port"), &HTTPClient::set_https_proxy);
+
 	ClassDB::bind_method(D_METHOD("query_string_from_dict", "fields"), &HTTPClient::query_string_from_dict);
 
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blocking_mode_enabled"), "set_blocking_mode", "is_blocking_mode_enabled");

+ 4 - 0
core/io/http_client.h

@@ -192,6 +192,10 @@ public:
 
 	virtual Error poll() = 0;
 
+	// Use empty string or -1 to unset
+	virtual void set_http_proxy(const String &p_host, int p_port);
+	virtual void set_https_proxy(const String &p_host, int p_port);
+
 	HTTPClient() {}
 	virtual ~HTTPClient() {}
 };

+ 86 - 7
core/io/http_client_tcp.cpp

@@ -70,9 +70,21 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss
 
 	connection = tcp_connection;
 
-	if (conn_host.is_valid_ip_address()) {
+	if (ssl && https_proxy_port != -1) {
+		proxy_client.instantiate(); // Needs proxy negotiation
+		server_host = https_proxy_host;
+		server_port = https_proxy_port;
+	} else if (!ssl && http_proxy_port != -1) {
+		server_host = http_proxy_host;
+		server_port = http_proxy_port;
+	} else {
+		server_host = conn_host;
+		server_port = conn_port;
+	}
+
+	if (server_host.is_valid_ip_address()) {
 		// Host contains valid IP
-		Error err = tcp_connection->connect_to_host(IPAddress(conn_host), p_port);
+		Error err = tcp_connection->connect_to_host(IPAddress(server_host), server_port);
 		if (err) {
 			status = STATUS_CANT_CONNECT;
 			return err;
@@ -81,7 +93,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_ss
 		status = STATUS_CONNECTING;
 	} else {
 		// Host contains hostname and needs to be resolved to IP
-		resolving = IP::get_singleton()->resolve_hostname_queue_item(conn_host);
+		resolving = IP::get_singleton()->resolve_hostname_queue_item(server_host);
 		status = STATUS_RESOLVING;
 	}
 
@@ -134,7 +146,12 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<
 	ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
 	ERR_FAIL_COND_V(connection.is_null(), ERR_INVALID_DATA);
 
-	String request = String(_methods[p_method]) + " " + p_url + " HTTP/1.1\r\n";
+	String uri = p_url;
+	if (!ssl && http_proxy_port != -1) {
+		uri = vformat("http://%s:%d%s", conn_host, conn_port, p_url);
+	}
+
+	String request = String(_methods[p_method]) + " " + uri + " HTTP/1.1\r\n";
 	bool add_host = true;
 	bool add_clen = p_body_size > 0;
 	bool add_uagent = true;
@@ -229,6 +246,7 @@ void HTTPClientTCP::close() {
 	}
 
 	connection.unref();
+	proxy_client.unref();
 	status = STATUS_DISCONNECTED;
 	head_request = false;
 	if (resolving != IP::RESOLVER_INVALID_ID) {
@@ -265,7 +283,7 @@ Error HTTPClientTCP::poll() {
 
 					Error err = ERR_BUG; // Should be at least one entry.
 					while (ip_candidates.size() > 0) {
-						err = tcp_connection->connect_to_host(ip_candidates.front(), conn_port);
+						err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port);
 						if (err == OK) {
 							break;
 						}
@@ -294,7 +312,48 @@ Error HTTPClientTCP::poll() {
 					return OK;
 				} break;
 				case StreamPeerTCP::STATUS_CONNECTED: {
-					if (ssl) {
+					if (ssl && proxy_client.is_valid()) {
+						Error err = proxy_client->poll();
+						if (err == ERR_UNCONFIGURED) {
+							proxy_client->set_connection(tcp_connection);
+							const Vector<String> headers;
+							err = proxy_client->request(METHOD_CONNECT, vformat("%s:%d", conn_host, conn_port), headers, nullptr, 0);
+							if (err != OK) {
+								status = STATUS_CANT_CONNECT;
+								return err;
+							}
+						} else if (err != OK) {
+							status = STATUS_CANT_CONNECT;
+							return err;
+						}
+						switch (proxy_client->get_status()) {
+							case STATUS_REQUESTING: {
+								return OK;
+							} break;
+							case STATUS_BODY: {
+								proxy_client->read_response_body_chunk();
+								return OK;
+							} break;
+							case STATUS_CONNECTED: {
+								if (proxy_client->get_response_code() != RESPONSE_OK) {
+									status = STATUS_CANT_CONNECT;
+									return ERR_CANT_CONNECT;
+								}
+								proxy_client.unref();
+								return OK;
+							}
+							case STATUS_DISCONNECTED:
+							case STATUS_RESOLVING:
+							case STATUS_CONNECTING: {
+								status = STATUS_CANT_CONNECT;
+								ERR_FAIL_V(ERR_BUG);
+							} break;
+							default: {
+								status = STATUS_CANT_CONNECT;
+								return ERR_CANT_CONNECT;
+							} break;
+						}
+					} else if (ssl) {
 						Ref<StreamPeerSSL> ssl;
 						if (!handshaking) {
 							// Connect the StreamPeerSSL and start handshaking
@@ -344,7 +403,7 @@ Error HTTPClientTCP::poll() {
 					Error err = ERR_CANT_CONNECT;
 					while (ip_candidates.size() > 0) {
 						tcp_connection->disconnect_from_host();
-						err = tcp_connection->connect_to_host(ip_candidates.pop_front(), conn_port);
+						err = tcp_connection->connect_to_host(ip_candidates.pop_front(), server_port);
 						if (err == OK) {
 							return OK;
 						}
@@ -678,6 +737,26 @@ int HTTPClientTCP::get_read_chunk_size() const {
 	return read_chunk_size;
 }
 
+void HTTPClientTCP::set_http_proxy(const String &p_host, int p_port) {
+	if (p_host.is_empty() || p_port == -1) {
+		http_proxy_host = "";
+		http_proxy_port = -1;
+	} else {
+		http_proxy_host = p_host;
+		http_proxy_port = p_port;
+	}
+}
+
+void HTTPClientTCP::set_https_proxy(const String &p_host, int p_port) {
+	if (p_host.is_empty() || p_port == -1) {
+		https_proxy_host = "";
+		https_proxy_port = -1;
+	} else {
+		https_proxy_host = p_host;
+		https_proxy_port = p_port;
+	}
+}
+
 HTTPClientTCP::HTTPClientTCP() {
 	tcp_connection.instantiate();
 }

+ 10 - 1
core/io/http_client_tcp.h

@@ -38,8 +38,14 @@ private:
 	Status status = STATUS_DISCONNECTED;
 	IP::ResolverID resolving = IP::RESOLVER_INVALID_ID;
 	Array ip_candidates;
-	int conn_port = -1;
+	int conn_port = -1; // Server to make requests to
 	String conn_host;
+	int server_port = -1; // Server to connect to (might be a proxy server)
+	String server_host;
+	int http_proxy_port = -1; // Proxy server for http requests
+	String http_proxy_host;
+	int https_proxy_port = -1; // Proxy server for https requests
+	String https_proxy_host;
 	bool ssl = false;
 	bool ssl_verify_host = false;
 	bool blocking = false;
@@ -58,6 +64,7 @@ private:
 
 	Ref<StreamPeerTCP> tcp_connection;
 	Ref<StreamPeer> connection;
+	Ref<HTTPClientTCP> proxy_client; // Negotiate with proxy server
 
 	int response_num = 0;
 	Vector<String> response_headers;
@@ -87,6 +94,8 @@ public:
 	void set_read_chunk_size(int p_size) override;
 	int get_read_chunk_size() const override;
 	Error poll() override;
+	void set_http_proxy(const String &p_host, int p_port) override;
+	void set_https_proxy(const String &p_host, int p_port) override;
 	HTTPClientTCP();
 };
 

+ 18 - 0
doc/classes/HTTPClient.xml

@@ -173,6 +173,24 @@
 				Sends the body data raw, as a byte array and does not encode it in any way.
 			</description>
 		</method>
+		<method name="set_http_proxy">
+			<return type="void" />
+			<argument index="0" name="host" type="String" />
+			<argument index="1" name="port" type="int" />
+			<description>
+				Sets the proxy server for HTTP requests.
+				The proxy server is unset if [code]host[/code] is empty or [code]port[/code] is -1.
+			</description>
+		</method>
+		<method name="set_https_proxy">
+			<return type="void" />
+			<argument index="0" name="host" type="String" />
+			<argument index="1" name="port" type="int" />
+			<description>
+				Sets the proxy server for HTTPS requests.
+				The proxy server is unset if [code]host[/code] is empty or [code]port[/code] is -1.
+			</description>
+		</method>
 	</methods>
 	<members>
 		<member name="blocking_mode_enabled" type="bool" setter="set_blocking_mode" getter="is_blocking_mode_enabled" default="false">