From 2970061a73f3cc482b2f1e4dc4ec42e32568134e Mon Sep 17 00:00:00 2001 From: Leon Krause Date: Thu, 26 Oct 2017 03:39:41 +0200 Subject: [PATCH] Implement HTTPClient in HTML5 platform Limitations: - Subject to same-origin policy - No persistent connection (but simulated for compatibility) - No blocking mode - No StreamPeer access - No chunked responses - Cannot disable host verification --- core/io/http_client.cpp | 135 +++++---- core/io/http_client.h | 14 +- platform/javascript/SCsub | 7 + platform/javascript/http_client.h.inc | 48 +++ .../javascript/http_client_javascript.cpp | 282 ++++++++++++++++++ platform/javascript/http_request.h | 72 +++++ platform/javascript/http_request.js | 145 +++++++++ 7 files changed, 633 insertions(+), 70 deletions(-) create mode 100644 platform/javascript/http_client.h.inc create mode 100644 platform/javascript/http_client_javascript.cpp create mode 100644 platform/javascript/http_request.h create mode 100644 platform/javascript/http_request.js diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 46d52384e51..5097898314e 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" +#ifndef JAVASCRIPT_ENABLED Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { close(); @@ -405,38 +406,6 @@ Error HTTPClient::poll() { return OK; } -Dictionary HTTPClient::_get_response_headers_as_dictionary() { - - List rh; - get_response_headers(&rh); - Dictionary ret; - for (const List::Element *E = rh.front(); E; E = E->next()) { - String s = E->get(); - int sp = s.find(":"); - if (sp == -1) - continue; - String key = s.substr(0, sp).strip_edges(); - String value = s.substr(sp + 1, s.length()).strip_edges(); - ret[key] = value; - } - - return ret; -} - -PoolStringArray HTTPClient::_get_response_headers() { - - List rh; - get_response_headers(&rh); - PoolStringArray ret; - ret.resize(rh.size()); - int idx = 0; - for (const List::Element *E = rh.front(); E; E = E->next()) { - ret.set(idx++, E->get()); - } - - return ret; -} - int HTTPClient::get_response_body_length() const { return body_size; @@ -612,6 +581,74 @@ Error HTTPClient::_get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received } } +void HTTPClient::set_read_chunk_size(int p_size) { + ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24)); + read_chunk_size = p_size; +} + +HTTPClient::HTTPClient() { + + tcp_connection = StreamPeerTCP::create_ref(); + resolving = IP::RESOLVER_INVALID_ID; + status = STATUS_DISCONNECTED; + conn_port = 80; + body_size = 0; + chunked = false; + body_left = 0; + chunk_left = 0; + response_num = 0; + ssl = false; + blocking = false; + read_chunk_size = 4096; +} + +HTTPClient::~HTTPClient() { +} + +#endif // #ifndef JAVASCRIPT_ENABLED + +String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { + String query = ""; + Array keys = p_dict.keys(); + for (int i = 0; i < keys.size(); ++i) { + query += "&" + String(keys[i]).http_escape() + "=" + String(p_dict[keys[i]]).http_escape(); + } + query.erase(0, 1); + return query; +} + +Dictionary HTTPClient::_get_response_headers_as_dictionary() { + + List rh; + get_response_headers(&rh); + Dictionary ret; + for (const List::Element *E = rh.front(); E; E = E->next()) { + String s = E->get(); + int sp = s.find(":"); + if (sp == -1) + continue; + String key = s.substr(0, sp).strip_edges(); + String value = s.substr(sp + 1, s.length()).strip_edges(); + ret[key] = value; + } + + return ret; +} + +PoolStringArray HTTPClient::_get_response_headers() { + + List rh; + get_response_headers(&rh); + PoolStringArray ret; + ret.resize(rh.size()); + int idx = 0; + for (const List::Element *E = rh.front(); E; E = E->next()) { + ret.set(idx++, E->get()); + } + + return ret; +} + void HTTPClient::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "use_ssl", "verify_host"), &HTTPClient::connect_to_host, DEFVAL(false), DEFVAL(true)); @@ -717,37 +754,3 @@ void HTTPClient::_bind_methods() { BIND_ENUM_CONSTANT(RESPONSE_INSUFFICIENT_STORAGE); BIND_ENUM_CONSTANT(RESPONSE_NOT_EXTENDED); } - -void HTTPClient::set_read_chunk_size(int p_size) { - ERR_FAIL_COND(p_size < 256 || p_size > (1 << 24)); - read_chunk_size = p_size; -} - -String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { - String query = ""; - Array keys = p_dict.keys(); - for (int i = 0; i < keys.size(); ++i) { - query += "&" + String(keys[i]).http_escape() + "=" + String(p_dict[keys[i]]).http_escape(); - } - query.erase(0, 1); - return query; -} - -HTTPClient::HTTPClient() { - - tcp_connection = StreamPeerTCP::create_ref(); - resolving = IP::RESOLVER_INVALID_ID; - status = STATUS_DISCONNECTED; - conn_port = 80; - body_size = 0; - chunked = false; - body_left = 0; - chunk_left = 0; - response_num = 0; - ssl = false; - blocking = false; - read_chunk_size = 4096; -} - -HTTPClient::~HTTPClient() { -} diff --git a/core/io/http_client.h b/core/io/http_client.h index f8a3349e6e7..db5dd115bd8 100644 --- a/core/io/http_client.h +++ b/core/io/http_client.h @@ -131,6 +131,7 @@ public: }; private: +#ifndef JAVASCRIPT_ENABLED Status status; IP::ResolverID resolving; int conn_port; @@ -152,14 +153,19 @@ private: int response_num; Vector response_headers; - - static void _bind_methods(); - PoolStringArray _get_response_headers(); - Dictionary _get_response_headers_as_dictionary(); int read_chunk_size; Error _get_http_data(uint8_t *p_buffer, int p_bytes, int &r_received); +#else +#include "platform/javascript/http_client.h.inc" +#endif + + PoolStringArray _get_response_headers(); + Dictionary _get_response_headers_as_dictionary(); + + static void _bind_methods(); + public: //Error connect_and_get(const String& p_url,bool p_verify_host=true); //connects to a full url and perform request Error connect_to_host(const String &p_host, int p_port, bool p_ssl = false, bool p_verify_host = true); diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index cfc0741318c..e3015d87b95 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -7,6 +7,7 @@ javascript_files = [ "audio_driver_javascript.cpp", "javascript_main.cpp", "power_javascript.cpp", + "http_client_javascript.cpp", "javascript_eval.cpp", ] @@ -42,6 +43,12 @@ else: js = env.Program(['#bin/godot'] + implicit_targets, javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js')[0]; zip_files.append(InstallAs(zip_dir.File('godot.js'), js)) +js_libraries = [] +js_libraries.append(env.File('http_request.js')) +for lib in js_libraries: + env.Append(LINKFLAGS=['--js-library', lib.path]) +env.Depends(js, js_libraries) + postjs = env.File('engine.js') env.Depends(js, [prejs, postjs]) env.Append(LINKFLAGS=['--pre-js', prejs.path]) diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc new file mode 100644 index 00000000000..9e4edf7848c --- /dev/null +++ b/platform/javascript/http_client.h.inc @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* http_client.h.inc */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +// HTTPClient's additional private members in the javascript platform + +Error prepare_request(Method p_method, const String &p_url, const Vector &p_headers); + +int xhr_id; +int read_limit; +int response_read_offset; +Status status; + +String host; +int port; +bool use_tls; +String username; +String password; + +int polled_response_code; +String polled_response_header; +PoolByteArray polled_response; diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp new file mode 100644 index 00000000000..0b105dcb405 --- /dev/null +++ b/platform/javascript/http_client_javascript.cpp @@ -0,0 +1,282 @@ +/*************************************************************************/ +/* http_client_javascript.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "http_request.h" +#include "io/http_client.h" + +Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { + + close(); + if (p_ssl && !p_verify_host) { + WARN_PRINT("Disabling HTTPClient's host verification is not supported for the HTML5 platform, host will be verified"); + } + + host = p_host; + if (host.begins_with("http://")) { + host.replace_first("http://", ""); + } else if (host.begins_with("https://")) { + host.replace_first("https://", ""); + } + + status = host.is_valid_ip_address() ? STATUS_CONNECTING : STATUS_RESOLVING; + port = p_port; + use_tls = p_ssl; + return OK; +} + +void HTTPClient::set_connection(const Ref &p_connection) { + + ERR_EXPLAIN("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform"); + ERR_FAIL(); +} + +Ref HTTPClient::get_connection() const { + + ERR_EXPLAIN("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform"); + ERR_FAIL_V(REF()); +} + +Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector &p_headers) { + + ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(host.empty(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED); + + static const char *_methods[HTTPClient::METHOD_MAX] = { + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "OPTIONS", + "TRACE", + "CONNECT" + }; + + String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + "/" + p_url; + godot_xhr_reset(xhr_id); + godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(), + username.empty() ? NULL : username.utf8().get_data(), + password.empty() ? NULL : password.utf8().get_data()); + + for (int i = 0; i < p_headers.size(); i++) { + int header_separator = p_headers[i].find(": "); + ERR_FAIL_COND_V(header_separator < 0, ERR_INVALID_PARAMETER); + godot_xhr_set_request_header(xhr_id, + p_headers[i].left(header_separator).utf8().get_data(), + p_headers[i].right(header_separator + 2).utf8().get_data()); + } + response_read_offset = 0; + status = STATUS_REQUESTING; + return OK; +} + +Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector &p_headers, const PoolVector &p_body) { + + Error err = prepare_request(p_method, p_url, p_headers); + if (err != OK) + return err; + PoolByteArray::Read read = p_body.read(); + godot_xhr_send_data(xhr_id, read.ptr(), p_body.size()); + return OK; +} + +Error HTTPClient::request(Method p_method, const String &p_url, const Vector &p_headers, const String &p_body) { + + Error err = prepare_request(p_method, p_url, p_headers); + if (err != OK) + return err; + godot_xhr_send_string(xhr_id, p_body.utf8().get_data()); + return OK; +} + +void HTTPClient::close() { + + host = ""; + port = -1; + use_tls = false; + status = STATUS_DISCONNECTED; + polled_response.resize(0); + polled_response_code = 0; + polled_response_header = String(); + godot_xhr_reset(xhr_id); +} + +HTTPClient::Status HTTPClient::get_status() const { + + return status; +} + +bool HTTPClient::has_response() const { + + return !polled_response_header.empty(); +} + +bool HTTPClient::is_response_chunked() const { + + // TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream + return false; +} + +int HTTPClient::get_response_code() const { + + return polled_response_code; +} + +Error HTTPClient::get_response_headers(List *r_response) { + + if (!polled_response_header.size()) + return ERR_INVALID_PARAMETER; + + Vector header_lines = polled_response_header.split("\r\n", false); + for (int i = 0; i < header_lines.size(); ++i) { + r_response->push_back(header_lines[i]); + } + polled_response_header = String(); + return OK; +} + +int HTTPClient::get_response_body_length() const { + + return polled_response.size(); +} + +PoolByteArray HTTPClient::read_response_body_chunk() { + + ERR_FAIL_COND_V(status != STATUS_BODY, PoolByteArray()); + + int to_read = MIN(read_limit, polled_response.size() - response_read_offset); + PoolByteArray chunk; + chunk.resize(to_read); + PoolByteArray::Write write = chunk.write(); + PoolByteArray::Read read = polled_response.read(); + memcpy(write.ptr(), read.ptr() + response_read_offset, to_read); + write = PoolByteArray::Write(); + read = PoolByteArray::Read(); + response_read_offset += to_read; + + if (response_read_offset == polled_response.size()) { + status = STATUS_CONNECTED; + polled_response.resize(0); + polled_response_code = 0; + polled_response_header = String(); + godot_xhr_reset(xhr_id); + } + + return chunk; +} + +void HTTPClient::set_blocking_mode(bool p_enable) { + + ERR_EXPLAIN("HTTPClient blocking mode is not supported for the HTML5 platform"); + ERR_FAIL_COND(p_enable); +} + +bool HTTPClient::is_blocking_mode_enabled() const { + + return false; +} + +void HTTPClient::set_read_chunk_size(int p_size) { + + read_limit = p_size; +} + +Error HTTPClient::poll() { + + switch (status) { + + case STATUS_DISCONNECTED: + return ERR_UNCONFIGURED; + + case STATUS_RESOLVING: + status = STATUS_CONNECTING; + return OK; + + case STATUS_CONNECTING: + status = STATUS_CONNECTED; + return OK; + + case STATUS_CONNECTED: + case STATUS_BODY: + return OK; + + case STATUS_CONNECTION_ERROR: + return ERR_CONNECTION_ERROR; + + case STATUS_REQUESTING: + polled_response_code = godot_xhr_get_status(xhr_id); + int response_length = godot_xhr_get_response_length(xhr_id); + if (response_length == 0) { + godot_xhr_ready_state_t ready_state = godot_xhr_get_ready_state(xhr_id); + if (ready_state == XHR_READY_STATE_HEADERS_RECEIVED || ready_state == XHR_READY_STATE_LOADING) { + return OK; + } else { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + } + + status = STATUS_BODY; + + PoolByteArray bytes; + int len = godot_xhr_get_response_headers_length(xhr_id); + bytes.resize(len); + PoolByteArray::Write write = bytes.write(); + godot_xhr_get_response_headers(xhr_id, reinterpret_cast(write.ptr()), len); + write = PoolByteArray::Write(); + + PoolByteArray::Read read = bytes.read(); + polled_response_header = String::utf8(reinterpret_cast(read.ptr())); + read = PoolByteArray::Read(); + + polled_response.resize(response_length); + write = polled_response.write(); + godot_xhr_get_response(xhr_id, write.ptr(), response_length); + write = PoolByteArray::Write(); + break; + } + return OK; +} + +HTTPClient::HTTPClient() { + + xhr_id = godot_xhr_new(); + read_limit = 4096; + status = STATUS_DISCONNECTED; + port = -1; + use_tls = false; + polled_response_code = 0; +} + +HTTPClient::~HTTPClient() { + + godot_xhr_free(xhr_id); +} diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h new file mode 100644 index 00000000000..80ff3f0ba84 --- /dev/null +++ b/platform/javascript/http_request.h @@ -0,0 +1,72 @@ +/*************************************************************************/ +/* http_request.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef HTTP_REQUEST_H +#define HTTP_REQUEST_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + XHR_READY_STATE_UNSENT = 0, + XHR_READY_STATE_OPENED = 1, + XHR_READY_STATE_HEADERS_RECEIVED = 2, + XHR_READY_STATE_LOADING = 3, + XHR_READY_STATE_DONE = 4, +} godot_xhr_ready_state_t; + +extern int godot_xhr_new(); +extern void godot_xhr_reset(int p_xhr_id); +extern bool godot_xhr_free(int p_xhr_id); + +extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = NULL, const char *p_password = NULL); + +extern void godot_xhr_set_request_header(int p_xhr_id, const char *p_header, const char *p_value); + +extern void godot_xhr_send_null(int p_xhr_id); +extern void godot_xhr_send_string(int p_xhr_id, const char *p_data); +extern void godot_xhr_send_data(int p_xhr_id, const void *p_data, int p_len); +extern void godot_xhr_abort(int p_xhr_id); + +/* this is an HTTPClient::ResponseCode, not ::Status */ +extern int godot_xhr_get_status(int p_xhr_id); +extern godot_xhr_ready_state_t godot_xhr_get_ready_state(int p_xhr_id); + +extern int godot_xhr_get_response_headers_length(int p_xhr_id); +extern void godot_xhr_get_response_headers(int p_xhr_id, char *r_dst, int p_len); + +extern int godot_xhr_get_response_length(int p_xhr_id); +extern void godot_xhr_get_response(int p_xhr_id, void *r_dst, int p_len); + +#ifdef __cplusplus +} +#endif + +#endif /* HTTP_REQUEST_H */ diff --git a/platform/javascript/http_request.js b/platform/javascript/http_request.js new file mode 100644 index 00000000000..f30240b41bd --- /dev/null +++ b/platform/javascript/http_request.js @@ -0,0 +1,145 @@ +/*************************************************************************/ +/* http_request.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +var GodotHTTPRequest = { + + $GodotHTTPRequest: { + + requests: [], + + getUnusedRequestId: function() { + var idMax = GodotHTTPRequest.requests.length; + for (var potentialId = 0; potentialId < idMax; ++potentialId) { + if (GodotHTTPRequest.requests[potentialId] instanceof XMLHttpRequest) { + continue; + } + return potentialId; + } + GodotHTTPRequest.requests.push(null) + return idMax; + }, + + setupRequest: function(xhr) { + xhr.responseType = 'arraybuffer'; + }, + }, + + godot_xhr_new: function() { + var newId = GodotHTTPRequest.getUnusedRequestId(); + GodotHTTPRequest.requests[newId] = new XMLHttpRequest; + GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]); + return newId; + }, + + godot_xhr_reset: function(xhrId) { + GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest; + GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); + }, + + godot_xhr_free: function(xhrId) { + GodotHTTPRequest.requests[xhrId].abort(); + GodotHTTPRequest.requests[xhrId] = null; + }, + + godot_xhr_open: function(xhrId, method, url, user, password) { + user = user > 0 ? UTF8ToString(user) : null; + password = password > 0 ? UTF8ToString(password) : null; + GodotHTTPRequest.requests[xhrId].open(UTF8ToString(method), UTF8ToString(url), true, user, password); + }, + + godot_xhr_set_request_header: function(xhrId, header, value) { + GodotHTTPRequest.requests[xhrId].setRequestHeader(UTF8ToString(header), UTF8ToString(value)); + }, + + godot_xhr_send_null: function(xhrId) { + GodotHTTPRequest.requests[xhrId].send(); + }, + + godot_xhr_send_string: function(xhrId, strPtr) { + if (!strPtr) { + Module.printErr("Failed to send string per XHR: null pointer"); + return; + } + GodotHTTPRequest.requests[xhrId].send(UTF8ToString(strPtr)); + }, + + godot_xhr_send_data: function(xhrId, ptr, len) { + if (!ptr) { + Module.printErr("Failed to send data per XHR: null pointer"); + return; + } + if (len < 0) { + Module.printErr("Failed to send data per XHR: buffer length less than 0"); + return; + } + GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); + }, + + godot_xhr_abort: function(xhrId) { + GodotHTTPRequest.requests[xhrId].abort(); + }, + + godot_xhr_get_status: function(xhrId) { + return GodotHTTPRequest.requests[xhrId].status; + }, + + godot_xhr_get_ready_state: function(xhrId) { + return GodotHTTPRequest.requests[xhrId].readyState; + }, + + godot_xhr_get_response_headers_length: function(xhrId) { + var headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); + return headers === null ? 0 : lengthBytesUTF8(headers); + }, + + godot_xhr_get_response_headers: function(xhrId, dst, len) { + var str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); + if (str === null) + return; + var buf = new Uint8Array(len + 1); + stringToUTF8Array(str, buf, 0, buf.length); + buf = buf.subarray(0, -1); + HEAPU8.set(buf, dst); + }, + + godot_xhr_get_response_length: function(xhrId) { + var body = GodotHTTPRequest.requests[xhrId].response; + return body === null ? 0 : body.byteLength; + }, + + godot_xhr_get_response: function(xhrId, dst, len) { + var buf = GodotHTTPRequest.requests[xhrId].response; + if (buf === null) + return; + buf = new Uint8Array(buf).subarray(0, len); + HEAPU8.set(buf, dst); + }, +}; + +autoAddDeps(GodotHTTPRequest, "$GodotHTTPRequest"); +mergeInto(LibraryManager.library, GodotHTTPRequest);