[HTML5] Replace XMLHttpRequest with Fetch.

This has some advantages:
- Streaming/chunked response support.
- Broader headers support.
This commit is contained in:
Fabio Alessandrelli 2021-03-02 16:00:24 +01:00
parent 44a662a149
commit fd76977183
6 changed files with 367 additions and 302 deletions

View File

@ -17,7 +17,7 @@ sys_env.AddJSLibraries(
[ [
"js/libs/library_godot_audio.js", "js/libs/library_godot_audio.js",
"js/libs/library_godot_display.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_os.js",
"js/libs/library_godot_runtime.js", "js/libs/library_godot_runtime.js",
] ]

View File

@ -30,24 +30,21 @@
// HTTPClient's additional private members in the javascript platform // HTTPClient's additional private members in the javascript platform
Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers); Error make_request(Method p_method, const String &p_url, const Vector<String> &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 read_limit = 4096;
int response_read_offset = 0;
Status status = STATUS_DISCONNECTED; Status status = STATUS_DISCONNECTED;
String host; String host;
int port = -1; int port = -1;
bool use_tls = false; bool use_tls = false;
String username;
String password;
int polled_response_code = 0; int polled_response_code = 0;
String polled_response_header; Vector<String> response_headers;
PackedByteArray polled_response; Vector<uint8_t> response_buffer;
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
bool has_polled = false;
uint64_t last_polling_frame = 0; uint64_t last_polling_frame = 0;
#endif #endif

View File

@ -30,7 +30,38 @@
#include "core/io/http_client.h" #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<HTTPClient *>(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) { Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
close(); close();
@ -74,7 +105,7 @@ Ref<StreamPeer> HTTPClient::get_connection() const {
ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform."); 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<String> &p_headers) { Error HTTPClient::make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) {
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); 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_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); 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); ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url; String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url;
godot_xhr_reset(xhr_id); Vector<CharString> keeper;
godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(), Vector<const char *> c_strings;
username.is_empty() ? nullptr : username.utf8().get_data(),
password.is_empty() ? nullptr : password.utf8().get_data());
for (int i = 0; i < p_headers.size(); i++) { for (int i = 0; i < p_headers.size(); i++) {
int header_separator = p_headers[i].find(": "); keeper.push_back(p_headers[i].utf8());
ERR_FAIL_COND_V(header_separator < 0, ERR_INVALID_PARAMETER); c_strings.push_back(keeper[i].get_data());
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; if (js_id) {
godot_js_fetch_free(js_id);
}
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; status = STATUS_REQUESTING;
return OK; return OK;
} }
Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) { Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
Error err = prepare_request(p_method, p_url, p_headers);
if (err != OK)
return err;
if (p_body.is_empty()) { if (p_body.is_empty()) {
godot_xhr_send(xhr_id, nullptr, 0); return make_request(p_method, p_url, p_headers, nullptr, 0);
} else {
godot_xhr_send(xhr_id, p_body.ptr(), p_body.size());
} }
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<String> &p_headers, const String &p_body) { Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &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()) { if (p_body.is_empty()) {
godot_xhr_send(xhr_id, nullptr, 0); return make_request(p_method, p_url, p_headers, nullptr, 0);
} else {
const CharString cs = p_body.utf8();
godot_xhr_send(xhr_id, cs.get_data(), cs.length());
} }
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() { void HTTPClient::close() {
@ -130,10 +148,13 @@ void HTTPClient::close() {
port = -1; port = -1;
use_tls = false; use_tls = false;
status = STATUS_DISCONNECTED; status = STATUS_DISCONNECTED;
polled_response.resize(0);
polled_response_code = 0; polled_response_code = 0;
polled_response_header = String(); response_headers.resize(0);
godot_xhr_reset(xhr_id); response_buffer.resize(0);
if (js_id) {
godot_js_fetch_free(js_id);
js_id = 0;
}
} }
HTTPClient::Status HTTPClient::get_status() const { HTTPClient::Status HTTPClient::get_status() const {
@ -141,12 +162,11 @@ HTTPClient::Status HTTPClient::get_status() const {
} }
bool HTTPClient::has_response() const { bool HTTPClient::has_response() const {
return !polled_response_header.is_empty(); return response_headers.size() > 0;
} }
bool HTTPClient::is_response_chunked() const { bool HTTPClient::is_response_chunked() const {
// TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream return godot_js_fetch_is_chunked(js_id);
return false;
} }
int HTTPClient::get_response_code() const { int HTTPClient::get_response_code() const {
@ -154,36 +174,42 @@ int HTTPClient::get_response_code() const {
} }
Error HTTPClient::get_response_headers(List<String> *r_response) { Error HTTPClient::get_response_headers(List<String> *r_response) {
if (polled_response_header.is_empty()) if (!response_headers.size()) {
return ERR_INVALID_PARAMETER; return ERR_INVALID_PARAMETER;
Vector<String> 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; return OK;
} }
int HTTPClient::get_response_body_length() const { 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() { PackedByteArray HTTPClient::read_response_body_chunk() {
ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray()); ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
int to_read = MIN(read_limit, polled_response.size() - response_read_offset); if (response_buffer.size() != read_limit) {
PackedByteArray chunk; response_buffer.resize(read_limit);
chunk.resize(to_read); }
memcpy(chunk.ptrw(), polled_response.ptr() + response_read_offset, to_read); int read = godot_js_fetch_read_chunk(js_id, response_buffer.ptrw(), read_limit);
response_read_offset += to_read;
if (response_read_offset == polled_response.size()) { // Check if the stream is over.
status = STATUS_CONNECTED; godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id);
polled_response.resize(0); if (state == GODOT_JS_FETCH_STATE_DONE) {
godot_xhr_reset(xhr_id); 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; return chunk;
} }
@ -217,48 +243,48 @@ Error HTTPClient::poll() {
return OK; return OK;
case STATUS_CONNECTED: case STATUS_CONNECTED:
case STATUS_BODY:
return OK; 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: case STATUS_CONNECTION_ERROR:
return ERR_CONNECTION_ERROR; return ERR_CONNECTION_ERROR;
case STATUS_REQUESTING: { case STATUS_REQUESTING: {
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (!has_polled) {
has_polled = true;
} else {
// forcing synchronous requests is not possible on the web // forcing synchronous requests is not possible on the web
if (last_polling_frame == Engine::get_singleton()->get_process_frames()) { if (last_polling_frame == Engine::get_singleton()->get_process_frames()) {
WARN_PRINT("HTTPClient polled multiple times in one frame, " WARN_PRINT("HTTPClient polled multiple times in one frame, "
"but request cannot progress more than once per " "but request cannot progress more than once per "
"frame on the HTML5 platform."); "frame on the HTML5 platform.");
} }
}
last_polling_frame = Engine::get_singleton()->get_process_frames(); last_polling_frame = Engine::get_singleton()->get_process_frames();
#endif #endif
polled_response_code = godot_xhr_get_status(xhr_id); polled_response_code = godot_js_fetch_http_status_get(js_id);
if (godot_xhr_get_ready_state(xhr_id) != XHR_READY_STATE_DONE) { godot_js_fetch_state_t js_state = godot_js_fetch_state_get(js_id);
if (js_state == GODOT_JS_FETCH_STATE_REQUESTING) {
return OK; 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; status = STATUS_CONNECTION_ERROR;
return ERR_CONNECTION_ERROR; return ERR_CONNECTION_ERROR;
} }
status = STATUS_BODY; 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<char *>(bytes.ptrw()), len);
bytes.ptrw()[len] = 0;
polled_response_header = String::utf8(reinterpret_cast<const char *>(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; break;
} }
@ -269,9 +295,8 @@ Error HTTPClient::poll() {
} }
HTTPClient::HTTPClient() { HTTPClient::HTTPClient() {
xhr_id = godot_xhr_new();
} }
HTTPClient::~HTTPClient() { HTTPClient::~HTTPClient() {
godot_xhr_free(xhr_id); close();
} }

View File

@ -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 */

View File

@ -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);

View File

@ -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);