diff options
Diffstat (limited to 'core/io/http_client.cpp')
-rw-r--r-- | core/io/http_client.cpp | 135 |
1 files changed, 112 insertions, 23 deletions
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 9e301ccac5..2425bb6d69 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -30,6 +30,7 @@ #include "http_client.h" #include "io/stream_peer_ssl.h" +#include "version.h" const char *HTTPClient::_methods[METHOD_MAX] = { "GET", @@ -121,16 +122,30 @@ Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; } bool add_clen = p_body.size() > 0; + bool add_uagent = true; + bool add_accept = true; for (int i = 0; i < p_headers.size(); i++) { request += p_headers[i] + "\r\n"; - if (add_clen && p_headers[i].find("Content-Length:") == 0) { + if (add_clen && p_headers[i].findn("Content-Length:") == 0) { add_clen = false; } + if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { + add_uagent = false; + } + if (add_accept && p_headers[i].findn("Accept:") == 0) { + add_accept = false; + } } if (add_clen) { request += "Content-Length: " + itos(p_body.size()) + "\r\n"; // Should it add utf8 encoding? } + if (add_uagent) { + request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; + } + if (add_accept) { + request += "Accept: */*\r\n"; + } request += "\r\n"; CharString cs = request.utf8(); @@ -173,17 +188,31 @@ Error HTTPClient::request(Method p_method, const String &p_url, const Vector<Str } else { request += "Host: " + conn_host + ":" + itos(conn_port) + "\r\n"; } + bool add_uagent = true; + bool add_accept = true; bool add_clen = p_body.length() > 0; for (int i = 0; i < p_headers.size(); i++) { request += p_headers[i] + "\r\n"; - if (add_clen && p_headers[i].find("Content-Length:") == 0) { + if (add_clen && p_headers[i].findn("Content-Length:") == 0) { add_clen = false; } + if (add_uagent && p_headers[i].findn("User-Agent:") == 0) { + add_uagent = false; + } + if (add_accept && p_headers[i].findn("Accept:") == 0) { + add_accept = false; + } } if (add_clen) { request += "Content-Length: " + itos(p_body.utf8().length()) + "\r\n"; // Should it add utf8 encoding? } + if (add_uagent) { + request += "User-Agent: GodotEngine/" + String(VERSION_FULL_BUILD) + " (" + OS::get_singleton()->get_name() + ")\r\n"; + } + if (add_accept) { + request += "Accept: */*\r\n"; + } request += "\r\n"; request += p_body; @@ -248,7 +277,9 @@ void HTTPClient::close() { body_size = 0; body_left = 0; chunk_left = 0; + read_until_eof = false; response_num = 0; + handshaking = false; } Error HTTPClient::poll() { @@ -297,16 +328,40 @@ Error HTTPClient::poll() { } break; case StreamPeerTCP::STATUS_CONNECTED: { if (ssl) { - Ref<StreamPeerSSL> ssl = StreamPeerSSL::create(); - Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); - if (err != OK) { + Ref<StreamPeerSSL> ssl; + if (!handshaking) { + // Connect the StreamPeerSSL and start handshaking + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ssl->set_blocking_handshake_enabled(false); + Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); + if (err != OK) { + close(); + status = STATUS_SSL_HANDSHAKE_ERROR; + return ERR_CANT_CONNECT; + } + connection = ssl; + handshaking = true; + } else { + // We are already handshaking, which means we can use your already active SSL connection + ssl = static_cast<Ref<StreamPeerSSL> >(connection); + ssl->poll(); // Try to finish the handshake + } + + if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { + // Handshake has been successfull + handshaking = false; + status = STATUS_CONNECTED; + return OK; + } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { + // Handshake has failed close(); status = STATUS_SSL_HANDSHAKE_ERROR; return ERR_CANT_CONNECT; } - connection = ssl; + // ... we will need to poll more for handshake to finish + } else { + status = STATUS_CONNECTED; } - status = STATUS_CONNECTED; return OK; } break; case StreamPeerTCP::STATUS_ERROR: @@ -352,10 +407,17 @@ Error HTTPClient::poll() { chunked = false; body_left = 0; chunk_left = 0; + read_until_eof = false; response_str.clear(); response_headers.clear(); response_num = RESPONSE_OK; + // Per the HTTP 1.1 spec, keep-alive is the default, but in practice + // it's safe to assume it only if the explicit header is found, allowing + // to handle body-up-to-EOF responses on naive servers; that's what Curl + // and browsers do + bool keep_alive = false; + for (int i = 0; i < responses.size(); i++) { String header = responses[i].strip_edges(); @@ -365,13 +427,14 @@ Error HTTPClient::poll() { if (s.begins_with("content-length:")) { body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int(); body_left = body_size; - } - if (s.begins_with("transfer-encoding:")) { + } else if (s.begins_with("transfer-encoding:")) { String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges(); if (encoding == "chunked") { chunked = true; } + } else if (s.begins_with("connection: keep-alive")) { + keep_alive = true; } if (i == 0 && responses[i].begins_with("HTTP")) { @@ -384,11 +447,16 @@ Error HTTPClient::poll() { } } - if (body_size == 0 && !chunked) { + if (body_size || chunked) { - status = STATUS_CONNECTED; // Ready for new requests - } else { status = STATUS_BODY; + } else if (!keep_alive) { + + read_until_eof = true; + status = STATUS_BODY; + } else { + + status = STATUS_CONNECTED; } return OK; } @@ -484,7 +552,7 @@ PoolByteArray HTTPClient::read_response_body_chunk() { } else { int rec = 0; - err = _get_http_data(&chunk[chunk.size() - chunk_left], chunk_left, rec); + err = _get_http_data(&chunk.write[chunk.size() - chunk_left], chunk_left, rec); if (rec == 0) { break; } @@ -515,34 +583,53 @@ PoolByteArray HTTPClient::read_response_body_chunk() { } else { - int to_read = MIN(body_left, read_chunk_size); + int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; PoolByteArray ret; ret.resize(to_read); int _offset = 0; - while (to_read > 0) { + while (read_until_eof || to_read > 0) { int rec = 0; { PoolByteArray::Write w = ret.write(); err = _get_http_data(w.ptr() + _offset, to_read, rec); } - if (rec > 0) { - body_left -= rec; - to_read -= rec; - _offset += rec; - } else { + if (rec < 0) { if (to_read > 0) // Ended up reading less ret.resize(_offset); break; + } else { + _offset += rec; + if (!read_until_eof) { + body_left -= rec; + to_read -= rec; + } else { + if (rec < to_read) { + ret.resize(_offset); + err = ERR_FILE_EOF; + break; + } + ret.resize(_offset + to_read); + } } } - if (body_left == 0) { - status = STATUS_CONNECTED; + if (!read_until_eof) { + if (body_left == 0) { + status = STATUS_CONNECTED; + } + return ret; + } else { + if (err == ERR_FILE_EOF) { + err = OK; // EOF is expected here + close(); + return ret; + } } - return ret; } if (err != OK) { + close(); + if (err == ERR_FILE_EOF) { status = STATUS_DISCONNECTED; // Server disconnected @@ -602,10 +689,12 @@ HTTPClient::HTTPClient() { body_size = 0; chunked = false; body_left = 0; + read_until_eof = false; chunk_left = 0; response_num = 0; ssl = false; blocking = false; + handshaking = false; read_chunk_size = 4096; } |