From 222214c05fa621501191026064f9d6f6e02fa7b1 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Wed, 7 Feb 2024 14:21:23 +0100 Subject: [PATCH 1/2] [Web] Move EditorHTTPServer implementation to cpp. --- platform/web/export/editor_http_server.cpp | 224 +++++++++++++++++++++ platform/web/export/editor_http_server.h | 198 +----------------- 2 files changed, 232 insertions(+), 190 deletions(-) create mode 100644 platform/web/export/editor_http_server.cpp diff --git a/platform/web/export/editor_http_server.cpp b/platform/web/export/editor_http_server.cpp new file mode 100644 index 000000000000..87420e457f7b --- /dev/null +++ b/platform/web/export/editor_http_server.cpp @@ -0,0 +1,224 @@ +/**************************************************************************/ +/* editor_http_server.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 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. */ +/**************************************************************************/ + +#include "editor_http_server.h" + +void EditorHTTPServer::_clear_client() { + peer = Ref(); + tls = Ref(); + tcp = Ref(); + memset(req_buf, 0, sizeof(req_buf)); + time = 0; + req_pos = 0; +} + +void EditorHTTPServer::_set_internal_certs(Ref p_crypto) { + const String cache_path = EditorPaths::get_singleton()->get_cache_dir(); + const String key_path = cache_path.path_join("html5_server.key"); + const String crt_path = cache_path.path_join("html5_server.crt"); + bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path); + if (!regen) { + key = Ref(CryptoKey::create()); + cert = Ref(X509Certificate::create()); + if (key->load(key_path) != OK || cert->load(crt_path) != OK) { + regen = true; + } + } + if (regen) { + key = p_crypto->generate_rsa(2048); + key->save(key_path); + cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000"); + cert->save(crt_path); + } +} + +void EditorHTTPServer::_send_response() { + Vector psa = String((char *)req_buf).split("\r\n"); + int len = psa.size(); + ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); + + Vector req = psa[0].split(" ", false); + ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code."); + + // Wrong protocol + ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); + + const int query_index = req[1].find_char('?'); + const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index); + + const String req_file = path.get_file(); + const String req_ext = path.get_extension(); + const String cache_path = EditorPaths::get_singleton()->get_cache_dir().path_join("web"); + const String filepath = cache_path.path_join(req_file); + + if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) { + String s = "HTTP/1.1 404 Not Found\r\n"; + s += "Connection: Close\r\n"; + s += "\r\n"; + CharString cs = s.utf8(); + peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + return; + } + const String ctype = mimes[req_ext]; + + Ref f = FileAccess::open(filepath, FileAccess::READ); + ERR_FAIL_COND(f.is_null()); + String s = "HTTP/1.1 200 OK\r\n"; + s += "Connection: Close\r\n"; + s += "Content-Type: " + ctype + "\r\n"; + s += "Access-Control-Allow-Origin: *\r\n"; + s += "Cross-Origin-Opener-Policy: same-origin\r\n"; + s += "Cross-Origin-Embedder-Policy: require-corp\r\n"; + s += "Cache-Control: no-store, max-age=0\r\n"; + s += "\r\n"; + CharString cs = s.utf8(); + Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + if (err != OK) { + ERR_FAIL(); + } + + while (true) { + uint8_t bytes[4096]; + uint64_t read = f->get_buffer(bytes, 4096); + if (read == 0) { + break; + } + err = peer->put_data(bytes, read); + if (err != OK) { + ERR_FAIL(); + } + } +} + +void EditorHTTPServer::stop() { + server->stop(); + _clear_client(); +} + +Error EditorHTTPServer::listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) { + use_tls = p_use_tls; + if (use_tls) { + Ref crypto = Crypto::create(); + if (crypto.is_null()) { + return ERR_UNAVAILABLE; + } + if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) { + key = Ref(CryptoKey::create()); + Error err = key->load(p_tls_key); + ERR_FAIL_COND_V(err != OK, err); + cert = Ref(X509Certificate::create()); + err = cert->load(p_tls_cert); + ERR_FAIL_COND_V(err != OK, err); + } else { + _set_internal_certs(crypto); + } + } + return server->listen(p_port, p_address); +} + +bool EditorHTTPServer::is_listening() const { + return server->is_listening(); +} + +void EditorHTTPServer::poll() { + if (!server->is_listening()) { + return; + } + if (tcp.is_null()) { + if (!server->is_connection_available()) { + return; + } + tcp = server->take_connection(); + peer = tcp; + time = OS::get_singleton()->get_ticks_usec(); + } + if (OS::get_singleton()->get_ticks_usec() - time > 1000000) { + _clear_client(); + return; + } + if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + return; + } + + if (use_tls) { + if (tls.is_null()) { + tls = Ref(StreamPeerTLS::create()); + peer = tls; + if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) { + _clear_client(); + return; + } + } + tls->poll(); + if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { + // Still handshaking, keep waiting. + return; + } + if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { + _clear_client(); + return; + } + } + + while (true) { + char *r = (char *)req_buf; + int l = req_pos - 1; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + _send_response(); + _clear_client(); + return; + } + + int read = 0; + ERR_FAIL_COND(req_pos >= 4096); + Error err = peer->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) { + // Got an error + _clear_client(); + return; + } else if (read != 1) { + // Busy, wait next poll + return; + } + req_pos += read; + } +} + +EditorHTTPServer::EditorHTTPServer() { + mimes["html"] = "text/html"; + mimes["js"] = "application/javascript"; + mimes["json"] = "application/json"; + mimes["pck"] = "application/octet-stream"; + mimes["png"] = "image/png"; + mimes["svg"] = "image/svg"; + mimes["wasm"] = "application/wasm"; + server.instantiate(); + stop(); +} diff --git a/platform/web/export/editor_http_server.h b/platform/web/export/editor_http_server.h index 3f872885377d..02e8665ca667 100644 --- a/platform/web/export/editor_http_server.h +++ b/platform/web/export/editor_http_server.h @@ -51,199 +51,17 @@ class EditorHTTPServer : public RefCounted { uint8_t req_buf[4096]; int req_pos = 0; - void _clear_client() { - peer = Ref(); - tls = Ref(); - tcp = Ref(); - memset(req_buf, 0, sizeof(req_buf)); - time = 0; - req_pos = 0; - } - - void _set_internal_certs(Ref p_crypto) { - const String cache_path = EditorPaths::get_singleton()->get_cache_dir(); - const String key_path = cache_path.path_join("html5_server.key"); - const String crt_path = cache_path.path_join("html5_server.crt"); - bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path); - if (!regen) { - key = Ref(CryptoKey::create()); - cert = Ref(X509Certificate::create()); - if (key->load(key_path) != OK || cert->load(crt_path) != OK) { - regen = true; - } - } - if (regen) { - key = p_crypto->generate_rsa(2048); - key->save(key_path); - cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000"); - cert->save(crt_path); - } - } + void _clear_client(); + void _set_internal_certs(Ref p_crypto); + void _send_response(); public: - EditorHTTPServer() { - mimes["html"] = "text/html"; - mimes["js"] = "application/javascript"; - mimes["json"] = "application/json"; - mimes["pck"] = "application/octet-stream"; - mimes["png"] = "image/png"; - mimes["svg"] = "image/svg"; - mimes["wasm"] = "application/wasm"; - server.instantiate(); - stop(); - } - - void stop() { - server->stop(); - _clear_client(); - } - - Error listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) { - use_tls = p_use_tls; - if (use_tls) { - Ref crypto = Crypto::create(); - if (crypto.is_null()) { - return ERR_UNAVAILABLE; - } - if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) { - key = Ref(CryptoKey::create()); - Error err = key->load(p_tls_key); - ERR_FAIL_COND_V(err != OK, err); - cert = Ref(X509Certificate::create()); - err = cert->load(p_tls_cert); - ERR_FAIL_COND_V(err != OK, err); - } else { - _set_internal_certs(crypto); - } - } - return server->listen(p_port, p_address); - } - - bool is_listening() const { - return server->is_listening(); - } - - void _send_response() { - Vector psa = String((char *)req_buf).split("\r\n"); - int len = psa.size(); - ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); - - Vector req = psa[0].split(" ", false); - ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code."); - - // Wrong protocol - ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); - - const int query_index = req[1].find_char('?'); - const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index); - - const String req_file = path.get_file(); - const String req_ext = path.get_extension(); - const String cache_path = EditorPaths::get_singleton()->get_cache_dir().path_join("web"); - const String filepath = cache_path.path_join(req_file); - - if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) { - String s = "HTTP/1.1 404 Not Found\r\n"; - s += "Connection: Close\r\n"; - s += "\r\n"; - CharString cs = s.utf8(); - peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); - return; - } - const String ctype = mimes[req_ext]; - - Ref f = FileAccess::open(filepath, FileAccess::READ); - ERR_FAIL_COND(f.is_null()); - String s = "HTTP/1.1 200 OK\r\n"; - s += "Connection: Close\r\n"; - s += "Content-Type: " + ctype + "\r\n"; - s += "Access-Control-Allow-Origin: *\r\n"; - s += "Cross-Origin-Opener-Policy: same-origin\r\n"; - s += "Cross-Origin-Embedder-Policy: require-corp\r\n"; - s += "Cache-Control: no-store, max-age=0\r\n"; - s += "\r\n"; - CharString cs = s.utf8(); - Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); - if (err != OK) { - ERR_FAIL(); - } - - while (true) { - uint8_t bytes[4096]; - uint64_t read = f->get_buffer(bytes, 4096); - if (read == 0) { - break; - } - err = peer->put_data(bytes, read); - if (err != OK) { - ERR_FAIL(); - } - } - } - - void poll() { - if (!server->is_listening()) { - return; - } - if (tcp.is_null()) { - if (!server->is_connection_available()) { - return; - } - tcp = server->take_connection(); - peer = tcp; - time = OS::get_singleton()->get_ticks_usec(); - } - if (OS::get_singleton()->get_ticks_usec() - time > 1000000) { - _clear_client(); - return; - } - if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { - return; - } - - if (use_tls) { - if (tls.is_null()) { - tls = Ref(StreamPeerTLS::create()); - peer = tls; - if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) { - _clear_client(); - return; - } - } - tls->poll(); - if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { - // Still handshaking, keep waiting. - return; - } - if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { - _clear_client(); - return; - } - } - - while (true) { - char *r = (char *)req_buf; - int l = req_pos - 1; - if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { - _send_response(); - _clear_client(); - return; - } + EditorHTTPServer(); - int read = 0; - ERR_FAIL_COND(req_pos >= 4096); - Error err = peer->get_partial_data(&req_buf[req_pos], 1, read); - if (err != OK) { - // Got an error - _clear_client(); - return; - } else if (read != 1) { - // Busy, wait next poll - return; - } - req_pos += read; - } - } + void stop(); + Error listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert); + bool is_listening() const; + void poll(); }; #endif // WEB_EDITOR_HTTP_SERVER_H From fdf6fa82c3753534d367c6b07576c832417a86a3 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Wed, 7 Feb 2024 15:59:11 +0100 Subject: [PATCH 2/2] [Web] Move polling thread to EditorHTTPServer --- platform/web/export/editor_http_server.cpp | 93 ++++++++++++++-------- platform/web/export/editor_http_server.h | 9 ++- platform/web/export/export_plugin.cpp | 29 +------ platform/web/export/export_plugin.h | 5 -- 4 files changed, 72 insertions(+), 64 deletions(-) diff --git a/platform/web/export/editor_http_server.cpp b/platform/web/export/editor_http_server.cpp index 87420e457f7b..0ccd4b8782df 100644 --- a/platform/web/export/editor_http_server.cpp +++ b/platform/web/export/editor_http_server.cpp @@ -30,6 +30,17 @@ #include "editor_http_server.h" +void EditorHTTPServer::_server_thread_poll(void *data) { + EditorHTTPServer *web_server = static_cast(data); + while (!web_server->server_quit.get()) { + OS::get_singleton()->delay_usec(6900); + { + MutexLock lock(web_server->server_lock); + web_server->_poll(); + } + } +} + void EditorHTTPServer::_clear_client() { peer = Ref(); tls = Ref(); @@ -117,37 +128,7 @@ void EditorHTTPServer::_send_response() { } } -void EditorHTTPServer::stop() { - server->stop(); - _clear_client(); -} - -Error EditorHTTPServer::listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) { - use_tls = p_use_tls; - if (use_tls) { - Ref crypto = Crypto::create(); - if (crypto.is_null()) { - return ERR_UNAVAILABLE; - } - if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) { - key = Ref(CryptoKey::create()); - Error err = key->load(p_tls_key); - ERR_FAIL_COND_V(err != OK, err); - cert = Ref(X509Certificate::create()); - err = cert->load(p_tls_cert); - ERR_FAIL_COND_V(err != OK, err); - } else { - _set_internal_certs(crypto); - } - } - return server->listen(p_port, p_address); -} - -bool EditorHTTPServer::is_listening() const { - return server->is_listening(); -} - -void EditorHTTPServer::poll() { +void EditorHTTPServer::_poll() { if (!server->is_listening()) { return; } @@ -211,6 +192,52 @@ void EditorHTTPServer::poll() { } } +void EditorHTTPServer::stop() { + server_quit.set(true); + if (server_thread.is_started()) { + server_thread.wait_to_finish(); + } + if (server.is_valid()) { + server->stop(); + } + _clear_client(); +} + +Error EditorHTTPServer::listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) { + MutexLock lock(server_lock); + if (server->is_listening()) { + return ERR_ALREADY_IN_USE; + } + use_tls = p_use_tls; + if (use_tls) { + Ref crypto = Crypto::create(); + if (crypto.is_null()) { + return ERR_UNAVAILABLE; + } + if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) { + key = Ref(CryptoKey::create()); + Error err = key->load(p_tls_key); + ERR_FAIL_COND_V(err != OK, err); + cert = Ref(X509Certificate::create()); + err = cert->load(p_tls_cert); + ERR_FAIL_COND_V(err != OK, err); + } else { + _set_internal_certs(crypto); + } + } + Error err = server->listen(p_port, p_address); + if (err == OK) { + server_quit.set(false); + server_thread.start(_server_thread_poll, this); + } + return err; +} + +bool EditorHTTPServer::is_listening() const { + MutexLock lock(server_lock); + return server->is_listening(); +} + EditorHTTPServer::EditorHTTPServer() { mimes["html"] = "text/html"; mimes["js"] = "application/javascript"; @@ -222,3 +249,7 @@ EditorHTTPServer::EditorHTTPServer() { server.instantiate(); stop(); } + +EditorHTTPServer::~EditorHTTPServer() { + stop(); +} diff --git a/platform/web/export/editor_http_server.h b/platform/web/export/editor_http_server.h index 02e8665ca667..8f5f69cc1ea9 100644 --- a/platform/web/export/editor_http_server.h +++ b/platform/web/export/editor_http_server.h @@ -51,17 +51,24 @@ class EditorHTTPServer : public RefCounted { uint8_t req_buf[4096]; int req_pos = 0; + SafeNumeric server_quit; + Mutex server_lock; + Thread server_thread; + void _clear_client(); void _set_internal_certs(Ref p_crypto); void _send_response(); + void _poll(); + + static void _server_thread_poll(void *data); public: EditorHTTPServer(); + ~EditorHTTPServer(); void stop(); Error listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert); bool is_listening() const; - void poll(); }; #endif // WEB_EDITOR_HTTP_SERVER_H diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index e2967ee8b84c..706bfca8ba4b 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -584,7 +584,6 @@ bool EditorExportPlatformWeb::poll_export() { menu_options = preset.is_valid(); if (server->is_listening()) { if (menu_options == 0) { - MutexLock lock(server_lock); server->stop(); } else { menu_options += 1; @@ -603,7 +602,6 @@ int EditorExportPlatformWeb::get_options_count() const { Error EditorExportPlatformWeb::run(const Ref &p_preset, int p_option, int p_debug_flags) { if (p_option == 1) { - MutexLock lock(server_lock); server->stop(); return OK; } @@ -653,12 +651,8 @@ Error EditorExportPlatformWeb::run(const Ref &p_preset, int const String tls_cert = EDITOR_GET("export/web/tls_certificate"); // Restart server. - { - MutexLock lock(server_lock); - - server->stop(); - err = server->listen(bind_port, bind_ip, use_tls, tls_key, tls_cert); - } + server->stop(); + err = server->listen(bind_port, bind_ip, use_tls, tls_key, tls_cert); if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Error starting HTTP server: %d."), err)); return err; @@ -674,21 +668,9 @@ Ref EditorExportPlatformWeb::get_run_icon() const { return run_icon; } -void EditorExportPlatformWeb::_server_thread_poll(void *data) { - EditorExportPlatformWeb *ej = static_cast(data); - while (!ej->server_quit.get()) { - OS::get_singleton()->delay_usec(6900); - { - MutexLock lock(ej->server_lock); - ej->server->poll(); - } - } -} - EditorExportPlatformWeb::EditorExportPlatformWeb() { if (EditorNode::get_singleton()) { server.instantiate(); - server_thread.start(_server_thread_poll, this); #ifdef MODULE_SVG_ENABLED Ref img = memnew(Image); @@ -711,11 +693,4 @@ EditorExportPlatformWeb::EditorExportPlatformWeb() { } EditorExportPlatformWeb::~EditorExportPlatformWeb() { - if (server.is_valid()) { - server->stop(); - } - server_quit.set(true); - if (server_thread.is_started()) { - server_thread.wait_to_finish(); - } } diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index c73d6d679443..952d03cdb4a9 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -52,9 +52,6 @@ class EditorExportPlatformWeb : public EditorExportPlatform { int menu_options = 0; Ref server; - SafeNumeric server_quit; - Mutex server_lock; - Thread server_thread; String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const { String name = "web"; @@ -99,8 +96,6 @@ class EditorExportPlatformWeb : public EditorExportPlatform { Error _build_pwa(const Ref &p_preset, const String p_path, const Vector &p_shared_objects); Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); - static void _server_thread_poll(void *data); - public: virtual void get_preset_features(const Ref &p_preset, List *r_features) const override;