From fd7697718311338fa1d546ded4f8dc4a8a9ae8eb Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Tue, 2 Mar 2021 16:00:24 +0100 Subject: [HTML5] Replace XMLHttpRequest with Fetch. This has some advantages: - Streaming/chunked response support. - Broader headers support. --- platform/javascript/SCsub | 2 +- platform/javascript/http_client.h.inc | 13 +- platform/javascript/http_client_javascript.cpp | 183 ++++++++------- platform/javascript/http_request.h | 73 ------ platform/javascript/js/libs/library_godot_fetch.js | 258 +++++++++++++++++++++ .../js/libs/library_godot_http_request.js | 142 ------------ 6 files changed, 368 insertions(+), 303 deletions(-) delete mode 100644 platform/javascript/http_request.h create mode 100644 platform/javascript/js/libs/library_godot_fetch.js delete mode 100644 platform/javascript/js/libs/library_godot_http_request.js (limited to 'platform/javascript') diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 50b61c0db3..72e999b38d 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -17,7 +17,7 @@ sys_env.AddJSLibraries( [ "js/libs/library_godot_audio.js", "js/libs/library_godot_display.js", - "js/libs/library_godot_http_request.js", + "js/libs/library_godot_fetch.js", "js/libs/library_godot_os.js", "js/libs/library_godot_runtime.js", ] diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc index 2ef1ad5b83..842a93fcba 100644 --- a/platform/javascript/http_client.h.inc +++ b/platform/javascript/http_client.h.inc @@ -30,24 +30,21 @@ // HTTPClient's additional private members in the javascript platform -Error prepare_request(Method p_method, const String &p_url, const Vector &p_headers); +Error make_request(Method p_method, const String &p_url, const Vector &p_headers, const uint8_t *p_body, int p_body_len); +static void _parse_headers(int p_len, const char **p_headers, void *p_ref); -int xhr_id; +int js_id = 0; int read_limit = 4096; -int response_read_offset = 0; Status status = STATUS_DISCONNECTED; String host; int port = -1; bool use_tls = false; -String username; -String password; int polled_response_code = 0; -String polled_response_header; -PackedByteArray polled_response; +Vector response_headers; +Vector response_buffer; #ifdef DEBUG_ENABLED -bool has_polled = false; uint64_t last_polling_frame = 0; #endif diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index 1136ea299d..b79c965854 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -30,7 +30,38 @@ #include "core/io/http_client.h" -#include "http_request.h" +#ifdef __cplusplus +extern "C" { +#endif + +#include "stddef.h" + +typedef enum { + GODOT_JS_FETCH_STATE_REQUESTING = 0, + GODOT_JS_FETCH_STATE_BODY = 1, + GODOT_JS_FETCH_STATE_DONE = 2, + GODOT_JS_FETCH_STATE_ERROR = -1, +} godot_js_fetch_state_t; + +extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len); +extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref); +extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size); +extern void godot_js_fetch_free(int p_id); +extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id); +extern int godot_js_fetch_body_length_get(int p_id); +extern int godot_js_fetch_http_status_get(int p_id); +extern int godot_js_fetch_is_chunked(int p_id); + +#ifdef __cplusplus +} +#endif + +void HTTPClient::_parse_headers(int p_len, const char **p_headers, void *p_ref) { + HTTPClient *client = static_cast(p_ref); + for (int i = 0; i < p_len; i++) { + client->response_headers.push_back(String::utf8(p_headers[i])); + } +} Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { close(); @@ -74,7 +105,7 @@ Ref HTTPClient::get_connection() const { ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform."); } -Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector &p_headers) { +Error HTTPClient::make_request(Method p_method, const String &p_url, const Vector &p_headers, const uint8_t *p_body, int p_body_len) { ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform."); ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); @@ -83,46 +114,33 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER); 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.is_empty() ? nullptr : username.utf8().get_data(), - password.is_empty() ? nullptr : password.utf8().get_data()); - + Vector keeper; + Vector c_strings; 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()); + keeper.push_back(p_headers[i].utf8()); + c_strings.push_back(keeper[i].get_data()); + } + if (js_id) { + godot_js_fetch_free(js_id); } - response_read_offset = 0; + js_id = godot_js_fetch_create(_methods[p_method], url.utf8().get_data(), c_strings.ptrw(), c_strings.size(), p_body, p_body_len); status = STATUS_REQUESTING; return OK; } Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector &p_headers, const Vector &p_body) { - Error err = prepare_request(p_method, p_url, p_headers); - if (err != OK) - return err; if (p_body.is_empty()) { - godot_xhr_send(xhr_id, nullptr, 0); - } else { - godot_xhr_send(xhr_id, p_body.ptr(), p_body.size()); + return make_request(p_method, p_url, p_headers, nullptr, 0); } - return OK; + return make_request(p_method, p_url, p_headers, p_body.ptr(), p_body.size()); } 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; if (p_body.is_empty()) { - godot_xhr_send(xhr_id, nullptr, 0); - } else { - const CharString cs = p_body.utf8(); - godot_xhr_send(xhr_id, cs.get_data(), cs.length()); + return make_request(p_method, p_url, p_headers, nullptr, 0); } - return OK; + const CharString cs = p_body.utf8(); + return make_request(p_method, p_url, p_headers, (const uint8_t *)cs.get_data(), cs.size() - 1); } void HTTPClient::close() { @@ -130,10 +148,13 @@ void HTTPClient::close() { 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); + response_headers.resize(0); + response_buffer.resize(0); + if (js_id) { + godot_js_fetch_free(js_id); + js_id = 0; + } } HTTPClient::Status HTTPClient::get_status() const { @@ -141,12 +162,11 @@ HTTPClient::Status HTTPClient::get_status() const { } bool HTTPClient::has_response() const { - return !polled_response_header.is_empty(); + return response_headers.size() > 0; } bool HTTPClient::is_response_chunked() const { - // TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream - return false; + return godot_js_fetch_is_chunked(js_id); } int HTTPClient::get_response_code() const { @@ -154,36 +174,42 @@ int HTTPClient::get_response_code() const { } Error HTTPClient::get_response_headers(List *r_response) { - if (polled_response_header.is_empty()) + if (!response_headers.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(); + for (int i = 0; i < response_headers.size(); i++) { + r_response->push_back(response_headers[i]); + } + response_headers.clear(); return OK; } int HTTPClient::get_response_body_length() const { - return polled_response.size(); + return godot_js_fetch_body_length_get(js_id); } PackedByteArray HTTPClient::read_response_body_chunk() { ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); - int to_read = MIN(read_limit, polled_response.size() - response_read_offset); - PackedByteArray chunk; - chunk.resize(to_read); - memcpy(chunk.ptrw(), polled_response.ptr() + response_read_offset, to_read); - response_read_offset += to_read; - - if (response_read_offset == polled_response.size()) { - status = STATUS_CONNECTED; - polled_response.resize(0); - godot_xhr_reset(xhr_id); + if (response_buffer.size() != read_limit) { + response_buffer.resize(read_limit); + } + int read = godot_js_fetch_read_chunk(js_id, response_buffer.ptrw(), read_limit); + + // Check if the stream is over. + godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id); + if (state == GODOT_JS_FETCH_STATE_DONE) { + status = STATUS_DISCONNECTED; + } else if (state != GODOT_JS_FETCH_STATE_BODY) { + status = STATUS_CONNECTION_ERROR; } + PackedByteArray chunk; + if (!read) { + return chunk; + } + chunk.resize(read); + copymem(chunk.ptrw(), response_buffer.ptr(), read); return chunk; } @@ -217,48 +243,48 @@ Error HTTPClient::poll() { return OK; case STATUS_CONNECTED: - case STATUS_BODY: return OK; + case STATUS_BODY: { + godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id); + if (state == GODOT_JS_FETCH_STATE_DONE) { + status = STATUS_DISCONNECTED; + } else if (state != GODOT_JS_FETCH_STATE_BODY) { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + return OK; + } + case STATUS_CONNECTION_ERROR: return ERR_CONNECTION_ERROR; case STATUS_REQUESTING: { #ifdef DEBUG_ENABLED - if (!has_polled) { - has_polled = true; - } else { - // forcing synchronous requests is not possible on the web - if (last_polling_frame == Engine::get_singleton()->get_process_frames()) { - WARN_PRINT("HTTPClient polled multiple times in one frame, " - "but request cannot progress more than once per " - "frame on the HTML5 platform."); - } + // forcing synchronous requests is not possible on the web + if (last_polling_frame == Engine::get_singleton()->get_process_frames()) { + WARN_PRINT("HTTPClient polled multiple times in one frame, " + "but request cannot progress more than once per " + "frame on the HTML5 platform."); } last_polling_frame = Engine::get_singleton()->get_process_frames(); #endif - polled_response_code = godot_xhr_get_status(xhr_id); - if (godot_xhr_get_ready_state(xhr_id) != XHR_READY_STATE_DONE) { + polled_response_code = godot_js_fetch_http_status_get(js_id); + godot_js_fetch_state_t js_state = godot_js_fetch_state_get(js_id); + if (js_state == GODOT_JS_FETCH_STATE_REQUESTING) { return OK; - } else if (!polled_response_code) { + } else if (js_state == GODOT_JS_FETCH_STATE_ERROR) { + // Fetch is in error state. + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + if (godot_js_fetch_read_headers(js_id, &_parse_headers, this)) { + // Failed to parse headers. status = STATUS_CONNECTION_ERROR; return ERR_CONNECTION_ERROR; } - status = STATUS_BODY; - - PackedByteArray bytes; - int len = godot_xhr_get_response_headers_length(xhr_id); - bytes.resize(len + 1); - - godot_xhr_get_response_headers(xhr_id, reinterpret_cast(bytes.ptrw()), len); - bytes.ptrw()[len] = 0; - - polled_response_header = String::utf8(reinterpret_cast(bytes.ptr())); - - polled_response.resize(godot_xhr_get_response_length(xhr_id)); - godot_xhr_get_response(xhr_id, polled_response.ptrw(), polled_response.size()); break; } @@ -269,9 +295,8 @@ Error HTTPClient::poll() { } HTTPClient::HTTPClient() { - xhr_id = godot_xhr_new(); } HTTPClient::~HTTPClient() { - godot_xhr_free(xhr_id); + close(); } diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h deleted file mode 100644 index a9d6faade2..0000000000 --- a/platform/javascript/http_request.h +++ /dev/null @@ -1,73 +0,0 @@ -/*************************************************************************/ -/* http_request.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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 - -#include "stddef.h" - -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 void 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 = nullptr, const char *p_password = nullptr); - -extern void godot_xhr_set_request_header(int p_xhr_id, const char *p_header, const char *p_value); - -extern void godot_xhr_send(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/js/libs/library_godot_fetch.js b/platform/javascript/js/libs/library_godot_fetch.js new file mode 100644 index 0000000000..4ae6a23593 --- /dev/null +++ b/platform/javascript/js/libs/library_godot_fetch.js @@ -0,0 +1,258 @@ +/*************************************************************************/ +/* library_godot_fetch.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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. */ +/*************************************************************************/ + +const GodotFetch = { + $GodotFetch__deps: ['$GodotRuntime'], + $GodotFetch: { + + onread: function (id, result) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + if (result.value) { + obj.chunks.push(result.value); + } + obj.reading = false; + obj.done = result.done; + }, + + onresponse: function (id, response) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + let size = -1; + let compressed = false; + let chunked = false; + response.headers.forEach(function (value, header) { + const v = value.toLowerCase().trim(); + const h = header.toLowerCase().trim(); + if (h === 'content-encoding') { + compressed = true; + size = -1; + } else if (h === 'content-length') { + const len = Number.parseInt(value, 10); + if (!Number.isNaN(len) && !compressed) { + size = len; + } + } else if (h === 'transfer-encoding' && v === 'chunked') { + chunked = true; + } + }); + obj.bodySize = size; + obj.status = response.status; + obj.response = response; + obj.reader = response.body.getReader(); + obj.chunked = chunked; + }, + + onerror: function (id, err) { + GodotRuntime.error(err); + const obj = IDHandler.get(id); + if (!obj) { + return; + } + obj.error = err; + }, + + create: function (method, url, headers, body) { + const obj = { + request: null, + response: null, + reader: null, + error: null, + done: false, + reading: false, + status: 0, + chunks: [], + bodySize: -1, + }; + const id = IDHandler.add(obj); + const init = { + method: method, + headers: headers, + body: body, + }; + obj.request = fetch(url, init); + obj.request.then(GodotFetch.onresponse.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); + return id; + }, + + free: function (id) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + IDHandler.remove(id); + if (!obj.request) { + return; + } + // Try to abort + obj.request.then(function (response) { + response.abort(); + }).catch(function (e) { /* nothing to do */ }); + }, + + read: function (id) { + const obj = IDHandler.get(id); + if (!obj) { + return; + } + if (obj.reader && !obj.reading) { + if (obj.done) { + obj.reader = null; + return; + } + obj.reading = true; + obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id)); + } + }, + }, + + godot_js_fetch_create__sig: 'iii', + godot_js_fetch_create: function (p_method, p_url, p_headers, p_headers_size, p_body, p_body_size) { + const method = GodotRuntime.parseString(p_method); + const url = GodotRuntime.parseString(p_url); + const headers = GodotRuntime.parseStringArray(p_headers, p_headers_size); + const body = p_body_size ? GodotRuntime.heapSlice(HEAP8, p_body, p_body_size) : null; + return GodotFetch.create(method, url, headers.map(function (hv) { + const idx = hv.indexOf(':'); + if (idx <= 0) { + return []; + } + return [ + hv.slice(0, idx).trim(), + hv.slice(idx + 1).trim(), + ]; + }).filter(function (v) { + return v.length === 2; + }), body); + }, + + godot_js_fetch_state_get__sig: 'ii', + godot_js_fetch_state_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj) { + return -1; + } + if (obj.error) { + return -1; + } + if (!obj.response) { + return 0; + } + if (obj.reader) { + return 1; + } + if (obj.done) { + return 2; + } + return -1; + }, + + godot_js_fetch_http_status_get__sig: 'ii', + godot_js_fetch_http_status_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 0; + } + return obj.status; + }, + + godot_js_fetch_read_headers__sig: 'iii', + godot_js_fetch_read_headers: function (p_id, p_parse_cb, p_ref) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 1; + } + const cb = GodotRuntime.get_func(p_parse_cb); + const arr = []; + obj.response.headers.forEach(function (v, h) { + arr.push(`${h}:${v}`); + }); + const c_ptr = GodotRuntime.allocStringArray(arr); + cb(arr.length, c_ptr, p_ref); + GodotRuntime.freeStringArray(c_ptr, arr.length); + return 0; + }, + + godot_js_fetch_read_chunk__sig: 'ii', + godot_js_fetch_read_chunk: function (p_id, p_buf, p_buf_size) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return 0; + } + let to_read = p_buf_size; + const chunks = obj.chunks; + while (to_read && chunks.length) { + const chunk = obj.chunks[0]; + if (chunk.length > to_read) { + GodotRuntime.heapCopy(HEAP8, chunk.slice(0, to_read), p_buf); + chunks[0] = chunk.slice(to_read); + to_read = 0; + } else { + GodotRuntime.heapCopy(HEAP8, chunk, p_buf); + to_read -= chunk.length; + chunks.pop(); + } + } + if (!chunks.length) { + GodotFetch.read(p_id); + } + return p_buf_size - to_read; + }, + + godot_js_fetch_body_length_get__sig: 'ii', + godot_js_fetch_body_length_get: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return -1; + } + return obj.bodySize; + }, + + godot_js_fetch_is_chunked__sig: 'ii', + godot_js_fetch_is_chunked: function (p_id) { + const obj = IDHandler.get(p_id); + if (!obj || !obj.response) { + return -1; + } + return obj.chunked ? 1 : 0; + }, + + godot_js_fetch_free__sig: 'vi', + godot_js_fetch_free: function (id) { + GodotFetch.free(id); + }, +}; + +autoAddDeps(GodotFetch, '$GodotFetch'); +mergeInto(LibraryManager.library, GodotFetch); diff --git a/platform/javascript/js/libs/library_godot_http_request.js b/platform/javascript/js/libs/library_godot_http_request.js deleted file mode 100644 index 2412f87e81..0000000000 --- a/platform/javascript/js/libs/library_godot_http_request.js +++ /dev/null @@ -1,142 +0,0 @@ -/*************************************************************************/ -/* http_request.js */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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. */ -/*************************************************************************/ - -const GodotHTTPRequest = { - $GodotHTTPRequest__deps: ['$GodotRuntime'], - $GodotHTTPRequest: { - requests: [], - - getUnusedRequestId: function () { - const idMax = GodotHTTPRequest.requests.length; - for (let 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__sig: 'i', - godot_xhr_new: function () { - const newId = GodotHTTPRequest.getUnusedRequestId(); - GodotHTTPRequest.requests[newId] = new XMLHttpRequest(); - GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]); - return newId; - }, - - godot_xhr_reset__sig: 'vi', - godot_xhr_reset: function (xhrId) { - GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest(); - GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); - }, - - godot_xhr_free__sig: 'vi', - godot_xhr_free: function (xhrId) { - GodotHTTPRequest.requests[xhrId].abort(); - GodotHTTPRequest.requests[xhrId] = null; - }, - - godot_xhr_open__sig: 'viiiii', - godot_xhr_open: function (xhrId, method, url, p_user, p_password) { - const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null; - const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null; - GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password); - }, - - godot_xhr_set_request_header__sig: 'viii', - godot_xhr_set_request_header: function (xhrId, header, value) { - GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value)); - }, - - godot_xhr_send__sig: 'viii', - godot_xhr_send: function (xhrId, p_ptr, p_len) { - let data = null; - if (p_ptr && p_len) { - data = GodotRuntime.heapSlice(HEAP8, p_ptr, p_len); - } - GodotHTTPRequest.requests[xhrId].send(data); - }, - - godot_xhr_abort__sig: 'vi', - godot_xhr_abort: function (xhrId) { - GodotHTTPRequest.requests[xhrId].abort(); - }, - - godot_xhr_get_status__sig: 'ii', - godot_xhr_get_status: function (xhrId) { - return GodotHTTPRequest.requests[xhrId].status; - }, - - godot_xhr_get_ready_state__sig: 'ii', - godot_xhr_get_ready_state: function (xhrId) { - return GodotHTTPRequest.requests[xhrId].readyState; - }, - - godot_xhr_get_response_headers_length__sig: 'ii', - godot_xhr_get_response_headers_length: function (xhrId) { - const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); - return headers === null ? 0 : GodotRuntime.strlen(headers); - }, - - godot_xhr_get_response_headers__sig: 'viii', - godot_xhr_get_response_headers: function (xhrId, dst, len) { - const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); - if (str === null) { - return; - } - GodotRuntime.stringToHeap(str, dst, len); - }, - - godot_xhr_get_response_length__sig: 'ii', - godot_xhr_get_response_length: function (xhrId) { - const body = GodotHTTPRequest.requests[xhrId].response; - return body === null ? 0 : body.byteLength; - }, - - godot_xhr_get_response__sig: 'viii', - godot_xhr_get_response: function (xhrId, dst, len) { - let 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); -- cgit v1.2.3