From 9a87c2082362f24c36114b1cf57626fc5ed70b2e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 9 Sep 2026 23:43:24 +0300 Subject: [PATCH 01/29] add std::string util::lower_case, upper_case overloads --- src/util/stringutil.cpp | 8 ++++++++ src/util/stringutil.hpp | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 55529a398..8d80a82ab 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -543,6 +543,14 @@ static std::basic_string upper_case(const std::basic_string& str) return result; } +std::string util::lower_case(const std::string& str) { + return ::lower_case(str); +} + +std::string util::upper_case(const std::string& str) { + return ::upper_case(str); +} + std::wstring util::lower_case(const std::wstring& str) { return ::lower_case(str); } diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index c26f02741..3f2263efb 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -101,6 +101,8 @@ namespace util { std::string capitalized(const std::string& str); + std::string lower_case(const std::string& str); + std::string upper_case(const std::string& str); std::wstring lower_case(const std::wstring& str); std::wstring upper_case(const std::wstring& str); std::wstring capitalized(const std::wstring& str); From 74bb695807ea0a0816ee8ad3da4addd56b26d2cf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 9 Sep 2026 23:43:44 +0300 Subject: [PATCH 02/29] cleanup http requests (part 1) --- src/logic/scripting/lua/libs/libnetwork.cpp | 32 ++++---- src/network/Curl.cpp | 85 +++++---------------- src/network/Network.cpp | 23 +----- src/network/Network.hpp | 17 +---- src/network/commons.hpp | 33 ++++---- test/network/curltest.cpp | 26 ++++--- 6 files changed, 73 insertions(+), 143 deletions(-) diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 55db6e550..1493eb0f0 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -3,6 +3,7 @@ #include "engine/Engine.hpp" #include "network/Network.hpp" #include "devtools/Project.hpp" +#include "util/stringutil.hpp" #include #include @@ -91,24 +92,26 @@ static int perform_get(lua::State* L, network::Network& network, bool binary) { int currentRequestId = request_id++; - network.get( - url, + network::HttpRequest request {}; + request.url = std::move(url); + request.onResponse = [currentRequestId, binary](std::vector bytes) { push_event(NetworkEvent( RESPONSE, ResponseEventDto { 200, binary, currentRequestId, std::move(bytes)} )); - }, + }; + request.onReject = [currentRequestId, binary](int code, std::vector bytes) { push_event(NetworkEvent( RESPONSE, ResponseEventDto { code, binary, currentRequestId, std::move(bytes)} )); - }, - std::move(headers) - ); + }; + request.headers = std::move(headers); + network.request(std::move(request)); return lua::pushinteger(L, currentRequestId); } @@ -134,25 +137,28 @@ static int l_post(lua::State* L, network::Network& network) { auto headers = read_headers(L, 3); int currentRequestId = request_id++; - network.post( - url, - string, + network::HttpRequest request {}; + request.url = std::move(url); + request.body = string; + request.headers = std::move(headers); + request.headers.emplace_back("Content-Type: application/json"); + request.onResponse = [currentRequestId](std::vector bytes) { push_event(NetworkEvent( RESPONSE, ResponseEventDto { 200, false, currentRequestId, std::move(bytes)} )); - }, + }; + request.onReject = [currentRequestId](int code, std::vector bytes) { push_event(NetworkEvent( RESPONSE, ResponseEventDto { code, false, currentRequestId, std::move(bytes)} )); - }, - std::move(headers) - ); + }; + network.request(std::move(request)); return lua::pushinteger(L, currentRequestId); } diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 1caafde73..4aad9c612 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -23,21 +23,6 @@ static size_t write_callback( return size * nmemb; } -enum class RequestType { - GET, POST -}; - -struct Request { - RequestType type; - std::string url; - OnResponse onResponse; - OnReject onReject; - long maxSize; - bool followLocation = false; - std::string data; - std::vector headers; -}; - class CurlRequests : public Requests { CURLM* multiHandle; CURL* curl; @@ -50,7 +35,7 @@ class CurlRequests : public Requests { std::vector buffer; std::string url; - std::queue requests; + std::queue requests; public: CurlRequests(CURLM* multiHandle, CURL* curl) : multiHandle(multiHandle), curl(curl) { @@ -61,47 +46,12 @@ public: curl_easy_cleanup(curl); curl_multi_cleanup(multiHandle); } - void get( - const std::string& url, - OnResponse onResponse, - OnReject onReject, - std::vector headers, - long maxSize - ) override { - Request request { - RequestType::GET, - url, - onResponse, - onReject, - maxSize, - true, - "", - std::move(headers)}; + + void request(HttpRequest request) override { processRequest(std::move(request)); } - void post( - const std::string& url, - const std::string& data, - OnResponse onResponse, - OnReject onReject=nullptr, - std::vector headers = {}, - long maxSize=0 - ) override { - Request request { - RequestType::POST, - url, - onResponse, - onReject, - maxSize, - false, - "", - std::move(headers)}; - request.data = data; - processRequest(std::move(request)); - } - - void processRequest(Request request) { + void processRequest(HttpRequest request) { if (!url.empty()) { requests.push(request); return; @@ -113,7 +63,19 @@ public: buffer.clear(); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_POST, request.type == RequestType::POST); + switch (request.method) { + case HttpMethod::GET: + break; + case HttpMethod::POST: + curl_easy_setopt(curl, CURLOPT_POST, true); + break; + case HttpMethod::PUT: + curl_easy_setopt(curl, CURLOPT_UPLOAD, true); + break; + case HttpMethod::DELETE: + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + } curl_slist* hs = nullptr; @@ -121,16 +83,9 @@ public: hs = curl_slist_append(hs, header.c_str()); } - switch (request.type) { - case RequestType::GET: - break; - case RequestType::POST: - hs = curl_slist_append(hs, "Content-Type: application/json"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.data.length()); - curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.data.c_str()); - break; - default: - throw std::runtime_error("not implemented"); + if (!request.body.empty()) { + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.body.length()); + curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.body.data()); } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation); diff --git a/src/network/Network.cpp b/src/network/Network.cpp index a0e1eb606..7b526a29a 100644 --- a/src/network/Network.cpp +++ b/src/network/Network.cpp @@ -51,27 +51,8 @@ Network::Network(std::unique_ptr requests) Network::~Network() = default; -void Network::get( - const std::string& url, - OnResponse onResponse, - OnReject onReject, - std::vector headers, - long maxSize -) { - requests->get(url, onResponse, onReject, std::move(headers), maxSize); -} - -void Network::post( - const std::string& url, - const std::string& fieldsData, - OnResponse onResponse, - OnReject onReject, - std::vector headers, - long maxSize -) { - requests->post( - url, fieldsData, onResponse, onReject, std::move(headers), maxSize - ); +void Network::request(HttpRequest request) { + requests->request(std::move(request)); } Connection* Network::getConnection(u64id_t id, bool includePrivate) { diff --git a/src/network/Network.hpp b/src/network/Network.hpp index 6d5cd6d54..49f44af6c 100644 --- a/src/network/Network.hpp +++ b/src/network/Network.hpp @@ -68,22 +68,7 @@ namespace network { Network(std::unique_ptr requests); ~Network(); - void get( - const std::string& url, - OnResponse onResponse, - OnReject onReject = nullptr, - std::vector headers = {}, - long maxSize=0 - ); - - void post( - const std::string& url, - const std::string& fieldsData, - OnResponse onResponse, - OnReject onReject = nullptr, - std::vector headers = {}, - long maxSize=0 - ); + void request(HttpRequest request); [[nodiscard]] Connection* getConnection(u64id_t id, bool includePrivate); [[nodiscard]] Server* getServer(u64id_t id, bool includePrivate) const; diff --git a/src/network/commons.hpp b/src/network/commons.hpp index 8b834bb3e..893a88ebe 100644 --- a/src/network/commons.hpp +++ b/src/network/commons.hpp @@ -10,6 +10,10 @@ #include namespace network { + enum class HttpMethod { + GET, POST, PUT, DELETE, + }; + using OnResponse = std::function)>; using OnReject = std::function)>; using ConnectCallback = std::function; @@ -17,26 +21,23 @@ namespace network { using ServerDatagramCallback = std::function; using ClientDatagramCallback = std::function; + struct HttpRequest { + HttpMethod method; + std::string url; + std::string body; + std::vector headers; + + OnResponse onResponse; + OnReject onReject; + bool followLocation = false; + long maxSize = -1; + }; + class Requests { public: virtual ~Requests() {} - virtual void get( - const std::string& url, - OnResponse onResponse, - OnReject onReject=nullptr, - std::vector headers = {}, - long maxSize=0 - ) = 0; - - virtual void post( - const std::string& url, - const std::string& data, - OnResponse onResponse, - OnReject onReject=nullptr, - std::vector headers = {}, - long maxSize=0 - ) = 0; + virtual void request(HttpRequest request) = 0; [[nodiscard]] virtual size_t getTotalUpload() const = 0; [[nodiscard]] virtual size_t getTotalDownload() const = 0; diff --git a/test/network/curltest.cpp b/test/network/curltest.cpp index 42806c77c..44f87d17e 100644 --- a/test/network/curltest.cpp +++ b/test/network/curltest.cpp @@ -6,18 +6,20 @@ TEST(curltest, curltest) { NetworkSettings settings {}; auto network = network::Network::create(settings); - network->get( - "https://raw.githubusercontent.com/MihailRis/VoxelEngine-Cpp/refs/" - "heads/curl/res/content/base/blocks/lamp.json", - [](std::vector data) { - if (data.empty()) { - return; - } - auto view = std::string_view(data.data(), data.size()); - auto value = json::parse(view); - std::cout << value << std::endl; - }, [](auto, auto){} - ); + + network::HttpRequest request {}; + request.url = "https://raw.githubusercontent.com/MihailRis/VoxelEngine-Cpp/refs/" + "heads/curl/res/content/base/blocks/lamp.json"; + request.onResponse = [](std::vector data) { + if (data.empty()) { + return; + } + auto view = std::string_view(data.data(), data.size()); + auto value = json::parse(view); + std::cout << value << std::endl; + }; + network->request(std::move(request)); + std::cout << "upload: " << network->getTotalUpload() << " B" << std::endl; std::cout << "download: " << network->getTotalDownload() << " B" << std::endl; } From 018e4c9f2c5e480c13103b6d2b2f8eeb508244e3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 9 Sep 2026 23:47:23 +0300 Subject: [PATCH 03/29] remove HttpMethod enum --- src/network/Curl.cpp | 14 +------------- src/network/commons.hpp | 6 +----- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 4aad9c612..6e89418c9 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -63,19 +63,7 @@ public: buffer.clear(); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - switch (request.method) { - case HttpMethod::GET: - break; - case HttpMethod::POST: - curl_easy_setopt(curl, CURLOPT_POST, true); - break; - case HttpMethod::PUT: - curl_easy_setopt(curl, CURLOPT_UPLOAD, true); - break; - case HttpMethod::DELETE: - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - break; - } + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.method); curl_slist* hs = nullptr; diff --git a/src/network/commons.hpp b/src/network/commons.hpp index 893a88ebe..614821404 100644 --- a/src/network/commons.hpp +++ b/src/network/commons.hpp @@ -10,10 +10,6 @@ #include namespace network { - enum class HttpMethod { - GET, POST, PUT, DELETE, - }; - using OnResponse = std::function)>; using OnReject = std::function)>; using ConnectCallback = std::function; @@ -22,7 +18,7 @@ namespace network { using ClientDatagramCallback = std::function; struct HttpRequest { - HttpMethod method; + std::string method; std::string url; std::string body; std::vector headers; From 1c7bf2de01f46a70dbaaa9d85d8e6eaa2e7b5a8d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 9 Sep 2026 23:53:03 +0300 Subject: [PATCH 04/29] add network.request --- res/scripts/classes.lua | 27 ++++++++++++++++--- src/logic/scripting/lua/libs/libnetwork.cpp | 30 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 280c5ddeb..008d51d2f 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -100,8 +100,29 @@ local _udp_client_open_callbacks = {} local _http_response_callbacks = {} local _http_error_callbacks = {} +local http_request = network.__request +local http_get = network.__get +local http_get_binary = network.__get_binary +local http_post = network.__post +network.__request = nil +network.__get = nil +network.__get_binary = nil +network.__post = nil + +local function request(url, params) + local id = http_request(url, params) + if params.on_response then + _http_response_callbacks[id] = params.on_response + end + if params.on_error then + _http_error_callbacks[id] = params.on_error + end +end + +network.request = request + network.get = function(url, callback, errorCallback, headers) - local id = network.__get(url, headers) + local id = http_get(url, headers) if callback then _http_response_callbacks[id] = callback end @@ -111,7 +132,7 @@ network.get = function(url, callback, errorCallback, headers) end network.get_binary = function(url, callback, errorCallback, headers) - local id = network.__get_binary(url, headers) + local id = http_get_binary(url, headers) if callback then _http_response_callbacks[id] = callback end @@ -121,7 +142,7 @@ network.get_binary = function(url, callback, errorCallback, headers) end network.post = function(url, data, callback, errorCallback, headers) - local id = network.__post(url, data, headers) + local id = http_post(url, data, headers) if callback then _http_response_callbacks[id] = callback end diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 1493eb0f0..318dfcc7c 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -86,6 +86,35 @@ static std::vector read_headers(lua::State* L, int index) { static int request_id = 1; +static int l_request(lua::State* L, network::Network& network) { + network::HttpRequest request {}; + request.url = lua::require_lstring(L, 1); + + if (!lua::istable(L, 2)) { + throw std::runtime_error("table expected as argument #2"); + } + if (lua::getfield(L, "method", 2)) { + request.method = util::upper_case(lua::require_string(L, -1)); + lua::pop(L); + } + if (lua::getfield(L, "headers", 2)) { + request.headers = read_headers(L, -1); + lua::pop(L); + } + if (lua::getfield(L, "body", 2)) { + request.body = lua::require_lstring(L, -1); + lua::pop(L); + } + if (lua::getfield(L, "follow_location", 2)) { + request.followLocation = lua::toboolean(L, -1); + lua::pop(L); + } + + int currentRequestId = request_id++; + network.request(std::move(request)); + return lua::pushinteger(L, currentRequestId); +} + static int perform_get(lua::State* L, network::Network& network, bool binary) { std::string url(lua::require_lstring(L, 1)); auto headers = read_headers(L, 2); @@ -568,6 +597,7 @@ int wrap(lua_State* L) { } const luaL_Reg networklib[] = { + {"__request", wrap}, {"__get", wrap}, {"__get_binary", wrap}, {"__post", wrap}, From 894101a46aca6f958d5cdeeddc8bf6e741462676 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 9 Sep 2026 23:57:58 +0300 Subject: [PATCH 05/29] cleanup network library --- res/scripts/classes.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 008d51d2f..c6e25b0b9 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -104,10 +104,18 @@ local http_request = network.__request local http_get = network.__get local http_get_binary = network.__get_binary local http_post = network.__post +local open_tcp = network.__open_tcp +local open_udp = network.__open_udp +local connect_tcp = network.__connect_tcp +local connect_udp = network.__connect_udp network.__request = nil network.__get = nil network.__get_binary = nil network.__post = nil +network.__open_tcp = nil +network.__open_udp = nil +network.__connect_tcp = nil +network.__connect_udp = nil local function request(url, params) local id = http_request(url, params) @@ -152,7 +160,7 @@ network.post = function(url, data, callback, errorCallback, headers) end network.tcp_open = function (port, handler) - local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket) + local socket = setmetatable({id=open_tcp(port)}, ServerSocket) _tcp_server_callbacks[socket.id] = function(id) handler(setmetatable({id=id}, Socket)) @@ -162,7 +170,7 @@ end network.tcp_connect = function(address, port, callback, errorCallback) local socket = setmetatable({id=0}, Socket) - socket.id = network.__connect_tcp(address, port) + socket.id = connect_tcp(address, port) _tcp_client_callbacks[socket.id] = function() callback(socket) end if errorCallback then _tcp_client_error_callbacks[socket.id] = function(message) errorCallback(socket, message) end @@ -175,7 +183,7 @@ network.udp_open = function (port, datagramHandler) error "udp server cannot be opened without datagram handler" end - local socket = setmetatable({id=network.__open_udp(port)}, DatagramServerSocket) + local socket = setmetatable({id=open_udp(port)}, DatagramServerSocket) _udp_server_callbacks[socket.id] = function(address, port, data) datagramHandler(address, port, data, socket) @@ -190,7 +198,7 @@ network.udp_connect = function (address, port, datagramHandler, openCallback) end local socket = setmetatable({id=0}, WriteableSocket) - socket.id = network.__connect_udp(address, port) + socket.id = connect_udp(address, port) _udp_client_datagram_callbacks[socket.id] = datagramHandler if openCallback then From 03efd8c6696dd3a299bcf0555bf8c6e9b2903850 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 10 Sep 2026 00:01:03 +0300 Subject: [PATCH 06/29] fix --- src/network/Curl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 6e89418c9..872e6d680 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -63,7 +63,7 @@ public: buffer.clear(); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.method); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.method.c_str()); curl_slist* hs = nullptr; From 9226f362a77f7ace5ca9633373f9f6ee904328af Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 10 Sep 2026 20:01:00 +0300 Subject: [PATCH 07/29] cleanup network.* functions --- res/scripts/classes.lua | 48 +++++------- src/logic/scripting/lua/libs/libnetwork.cpp | 81 +-------------------- 2 files changed, 21 insertions(+), 108 deletions(-) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index c6e25b0b9..0f9f1ce0a 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -101,17 +101,11 @@ local _http_response_callbacks = {} local _http_error_callbacks = {} local http_request = network.__request -local http_get = network.__get -local http_get_binary = network.__get_binary -local http_post = network.__post local open_tcp = network.__open_tcp local open_udp = network.__open_udp local connect_tcp = network.__connect_tcp local connect_udp = network.__connect_udp network.__request = nil -network.__get = nil -network.__get_binary = nil -network.__post = nil network.__open_tcp = nil network.__open_udp = nil network.__connect_tcp = nil @@ -130,33 +124,31 @@ end network.request = request network.get = function(url, callback, errorCallback, headers) - local id = http_get(url, headers) - if callback then - _http_response_callbacks[id] = callback - end - if errorCallback then - _http_error_callbacks[id] = errorCallback - end + return request(url, { + headers = headers, + on_response = callback, + on_error = errorCallback, + }) end network.get_binary = function(url, callback, errorCallback, headers) - local id = http_get_binary(url, headers) - if callback then - _http_response_callbacks[id] = callback - end - if errorCallback then - _http_error_callbacks[id] = errorCallback - end + return request(url, { + headers = headers, + on_response = callback and (function (response) return callback(Bytearray(response)) end), + on_error = errorCallback + }) end -network.post = function(url, data, callback, errorCallback, headers) - local id = http_post(url, data, headers) - if callback then - _http_response_callbacks[id] = callback - end - if errorCallback then - _http_error_callbacks[id] = errorCallback - end +network.post = function(url, body, callback, errorCallback, headers) + return request(url, { + method = "POST", + headers = table.extend({ + "Content-Type: application/json" + }, headers), + body = body, + on_response = callback, + on_error = errorCallback, + }) end network.tcp_open = function (port, handler) diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 318dfcc7c..b10d12b4b 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -94,7 +94,7 @@ static int l_request(lua::State* L, network::Network& network) { throw std::runtime_error("table expected as argument #2"); } if (lua::getfield(L, "method", 2)) { - request.method = util::upper_case(lua::require_string(L, -1)); + request.method = lua::require_string(L, -1); lua::pop(L); } if (lua::getfield(L, "headers", 2)) { @@ -115,82 +115,6 @@ static int l_request(lua::State* L, network::Network& network) { return lua::pushinteger(L, currentRequestId); } -static int perform_get(lua::State* L, network::Network& network, bool binary) { - std::string url(lua::require_lstring(L, 1)); - auto headers = read_headers(L, 2); - - int currentRequestId = request_id++; - - network::HttpRequest request {}; - request.url = std::move(url); - request.onResponse = - [currentRequestId, binary](std::vector bytes) { - push_event(NetworkEvent( - RESPONSE, - ResponseEventDto { - 200, binary, currentRequestId, std::move(bytes)} - )); - }; - request.onReject = - [currentRequestId, binary](int code, std::vector bytes) { - push_event(NetworkEvent( - RESPONSE, - ResponseEventDto { - code, binary, currentRequestId, std::move(bytes)} - )); - }; - request.headers = std::move(headers); - network.request(std::move(request)); - return lua::pushinteger(L, currentRequestId); -} - -static int l_get(lua::State* L, network::Network& network) { - return perform_get(L, network, false); -} - -static int l_get_binary(lua::State* L, network::Network& network) { - return perform_get(L, network, true); -} - -static int l_post(lua::State* L, network::Network& network) { - std::string url(lua::require_lstring(L, 1)); - auto data = lua::tovalue(L, 2); - - std::string string; - if (data.isString()) { - string = data.asString(); - } else { - string = json::stringify(data, false); - } - - auto headers = read_headers(L, 3); - int currentRequestId = request_id++; - - network::HttpRequest request {}; - request.url = std::move(url); - request.body = string; - request.headers = std::move(headers); - request.headers.emplace_back("Content-Type: application/json"); - request.onResponse = - [currentRequestId](std::vector bytes) { - push_event(NetworkEvent( - RESPONSE, - ResponseEventDto { - 200, false, currentRequestId, std::move(bytes)} - )); - }; - request.onReject = - [currentRequestId](int code, std::vector bytes) { - push_event(NetworkEvent( - RESPONSE, - ResponseEventDto { - code, false, currentRequestId, std::move(bytes)} - )); - }; - network.request(std::move(request)); - return lua::pushinteger(L, currentRequestId); -} - static int l_close(lua::State* L, network::Network& network) { u64id_t id = lua::tointeger(L, 1); if (auto connection = network.getConnection(id, false)) { @@ -598,9 +522,6 @@ int wrap(lua_State* L) { const luaL_Reg networklib[] = { {"__request", wrap}, - {"__get", wrap}, - {"__get_binary", wrap}, - {"__post", wrap}, {"get_total_upload", wrap}, {"get_total_download", wrap}, {"find_free_port", wrap}, From 946728e54e65cf1e14fe7f5d277f75c7008ec45e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 10 Sep 2026 20:38:50 +0300 Subject: [PATCH 08/29] fix --- res/scripts/classes.lua | 6 +++++- src/logic/scripting/lua/libs/libnetwork.cpp | 13 +++++++++++++ src/network/Curl.cpp | 11 ++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 0f9f1ce0a..b044aa478 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -125,17 +125,21 @@ network.request = request network.get = function(url, callback, errorCallback, headers) return request(url, { + method = "GET", headers = headers, on_response = callback, on_error = errorCallback, + follow_location = true, }) end network.get_binary = function(url, callback, errorCallback, headers) return request(url, { + method = "GET", headers = headers, on_response = callback and (function (response) return callback(Bytearray(response)) end), - on_error = errorCallback + on_error = errorCallback, + follow_location = true, }) end diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index b10d12b4b..d8c9e7c98 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -111,6 +111,19 @@ static int l_request(lua::State* L, network::Network& network) { } int currentRequestId = request_id++; + request.onResponse = [currentRequestId](std::vector bytes) { + push_event(NetworkEvent( + RESPONSE, + ResponseEventDto {200, false, currentRequestId, std::move(bytes)} + )); + }; + request.onReject = [currentRequestId](int code, std::vector bytes) { + push_event(NetworkEvent( + RESPONSE, + ResponseEventDto {code, false, currentRequestId, std::move(bytes)} + )); + }; + network.request(std::move(request)); return lua::pushinteger(L, currentRequestId); } diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 872e6d680..3bbeb3489 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -80,6 +80,9 @@ public: curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0"); +#ifndef NDEBUG + // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); +#endif if (request.maxSize == 0) { curl_easy_setopt( curl, CURLOPT_MAXFILESIZE, std::numeric_limits::max() @@ -119,7 +122,8 @@ public: if(msg->msg == CURLMSG_DONE) { curl_multi_remove_handle(multiHandle, curl); } - int response; + int response = -1; + CURLcode result = msg->data.result; curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response); if (response == HTTP_OK) { long size; @@ -133,6 +137,11 @@ public: if (onResponse) { onResponse(std::move(buffer)); } + } else if (response == 0) { + logger.error() << curl_easy_strerror(result) << " (" << url << ")"; + if (onReject) { + onReject(response, {}); + } } else { logger.error() << "response code " << response << " (" << url << ")" From 0608a9ad3d5b65c4419b1342fa9cce97d294c1cb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 10 Sep 2026 20:42:22 +0300 Subject: [PATCH 09/29] update request error handling --- src/network/Curl.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 3bbeb3489..55e98988b 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -138,9 +138,15 @@ public: onResponse(std::move(buffer)); } } else if (response == 0) { - logger.error() << curl_easy_strerror(result) << " (" << url << ")"; + auto message = std::string(curl_easy_strerror(result)); + logger.error() << message << " (" << url << ")"; if (onReject) { - onReject(response, {}); + onReject( + response, + std::vector( + message.data(), message.data() + message.size() + ) + ); } } else { logger.error() From effa850337c4c1e243825255db605ac4b834b6ab Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 10 Sep 2026 21:08:12 +0300 Subject: [PATCH 10/29] add HttpRequest::verifySSL --- src/network/Curl.cpp | 4 +++- src/network/commons.hpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 55e98988b..ca64f43f3 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -71,10 +71,12 @@ public: hs = curl_slist_append(hs, header.c_str()); } + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.body.length()); if (!request.body.empty()) { - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.body.length()); curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.body.data()); } + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, request.verifySSL); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, request.verifySSL); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); diff --git a/src/network/commons.hpp b/src/network/commons.hpp index 614821404..46745bfbb 100644 --- a/src/network/commons.hpp +++ b/src/network/commons.hpp @@ -26,6 +26,7 @@ namespace network { OnResponse onResponse; OnReject onReject; bool followLocation = false; + bool verifySSL = true; long maxSize = -1; }; From 9510e06a26bf0375d9301ac4203d57647f88a119 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 10 Sep 2026 22:29:34 +0300 Subject: [PATCH 11/29] update Curl.cpp --- src/network/Curl.cpp | 105 +++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index ca64f43f3..52c2e4132 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -23,27 +23,27 @@ static size_t write_callback( return size * nmemb; } +struct ProcessingRequest { + CURL* curl; + HttpRequest request; + std::vector buffer; +}; + class CurlRequests : public Requests { CURLM* multiHandle; - CURL* curl; + std::vector> requests; size_t totalUpload = 0; size_t totalDownload = 0; - - OnResponse onResponse; - OnReject onReject; - std::vector buffer; - std::string url; - - std::queue requests; public: - CurlRequests(CURLM* multiHandle, CURL* curl) - : multiHandle(multiHandle), curl(curl) { + CurlRequests(CURLM* multiHandle) : multiHandle(multiHandle) { } virtual ~CurlRequests() { - curl_multi_remove_handle(multiHandle, curl); - curl_easy_cleanup(curl); + for (auto& entry : requests) { + curl_multi_remove_handle(multiHandle, entry->curl); + curl_easy_cleanup(entry->curl); + } curl_multi_cleanup(multiHandle); } @@ -52,17 +52,12 @@ public: } void processRequest(HttpRequest request) { - if (!url.empty()) { - requests.push(request); - return; - } - onResponse = request.onResponse; - onReject = request.onReject; - url = request.url; + auto curl = curl_easy_init(); - buffer.clear(); + auto entry = std::make_unique(); + entry->curl = curl; - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.method.c_str()); curl_slist* hs = nullptr; @@ -80,7 +75,7 @@ public: curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &entry->buffer); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0"); #ifndef NDEBUG // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); @@ -97,12 +92,13 @@ public: CURLMcode res = curl_multi_perform(multiHandle, &running); if (res != CURLM_OK) { auto message = curl_multi_strerror(res); - logger.error() << message << " (" << url << ")"; - if (onReject) { - onReject(HTTP_BAD_GATEWAY, {}); + logger.error() << message << " (" << request.url << ")"; + if (request.onReject) { + request.onReject(HTTP_BAD_GATEWAY, {}); } - url = ""; } + entry->request = std::move(request); + requests.push_back(std::move(entry)); } void update() override { @@ -112,15 +108,24 @@ public: CURLMcode res = curl_multi_perform(multiHandle, &running); if (res != CURLM_OK) { auto message = curl_multi_strerror(res); - logger.error() << message << " (" << url << ")"; - if (onReject) { - onReject(HTTP_BAD_GATEWAY, {}); - } - curl_multi_remove_handle(multiHandle, curl); - url = ""; + logger.error() << message; return; } if ((msg = curl_multi_info_read(multiHandle, &messagesLeft)) != nullptr) { + auto curl = msg->easy_handle; + auto found = std::find_if( + requests.begin(), requests.end(), + [curl](const std::unique_ptr& entry) { + return entry->curl == curl; + } + ); + if (found == requests.end()) { + logger.error() << "could not find request for cURL handle"; + return; + } + auto& entry = *found; + auto& req = entry->request; + if(msg->msg == CURLMSG_DONE) { curl_multi_remove_handle(multiHandle, curl); } @@ -135,15 +140,15 @@ public: if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) { totalDownload += size; } - totalDownload += buffer.size(); - if (onResponse) { - onResponse(std::move(buffer)); + totalDownload += entry->buffer.size(); + if (req.onResponse) { + req.onResponse(std::move(entry->buffer)); } } else if (response == 0) { auto message = std::string(curl_easy_strerror(result)); - logger.error() << message << " (" << url << ")"; - if (onReject) { - onReject( + logger.error() << message << " (" << req.url << ")"; + if (req.onReject) { + req.onReject( response, std::vector( message.data(), message.data() + message.size() @@ -152,21 +157,15 @@ public: } } else { logger.error() - << "response code " << response << " (" << url << ")" - << (buffer.empty() + << "response code " << response << " (" << req.url << ")" + << (entry->buffer.empty() ? "" - : std::to_string(buffer.size()) + " byte(s)"); - totalDownload += buffer.size(); - if (onReject) { - onReject(response, std::move(buffer)); + : std::to_string(entry->buffer.size()) + " byte(s)"); + totalDownload += entry->buffer.size(); + if (req.onReject) { + req.onReject(response, std::move(entry->buffer)); } } - url = ""; - } - if (url.empty() && !requests.empty()) { - auto request = std::move(requests.front()); - requests.pop(); - processRequest(std::move(request)); } } @@ -181,14 +180,14 @@ public: static std::unique_ptr create() { auto curl = curl_easy_init(); if (curl == nullptr) { - throw std::runtime_error("could not initialzie cURL"); + throw std::runtime_error("could not initialize cURL"); } auto multiHandle = curl_multi_init(); if (multiHandle == nullptr) { curl_easy_cleanup(curl); - throw std::runtime_error("could not initialzie cURL-multi"); + throw std::runtime_error("could not initialize cURL-multi"); } - return std::make_unique(multiHandle, curl); + return std::make_unique(multiHandle); } }; From f44d3355f90b94703a8c0ae6d4c05d756ab1fedb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 10 Sep 2026 22:45:18 +0300 Subject: [PATCH 12/29] fix curl handles lifetime --- src/network/Curl.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 52c2e4132..42d4817c8 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -24,9 +24,19 @@ static size_t write_callback( } struct ProcessingRequest { + CURLM* multiHandle; CURL* curl; HttpRequest request; std::vector buffer; + + ProcessingRequest(CURLM* multiHandle) : multiHandle(multiHandle) { + curl = curl_easy_init(); + } + + ~ProcessingRequest() { + curl_multi_remove_handle(multiHandle, curl); + curl_easy_cleanup(curl); + } }; class CurlRequests : public Requests { @@ -40,10 +50,7 @@ public: } virtual ~CurlRequests() { - for (auto& entry : requests) { - curl_multi_remove_handle(multiHandle, entry->curl); - curl_easy_cleanup(entry->curl); - } + requests.clear(); curl_multi_cleanup(multiHandle); } @@ -52,10 +59,8 @@ public: } void processRequest(HttpRequest request) { - auto curl = curl_easy_init(); - - auto entry = std::make_unique(); - entry->curl = curl; + auto entry = std::make_unique(multiHandle); + auto curl = entry->curl; curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.method.c_str()); @@ -123,9 +128,11 @@ public: logger.error() << "could not find request for cURL handle"; return; } - auto& entry = *found; + auto entry = std::move(*found); auto& req = entry->request; + requests.erase(found); + if(msg->msg == CURLMSG_DONE) { curl_multi_remove_handle(multiHandle, curl); } From a5f9eafe23435988ecdbf20023a17e3cdb055401 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 11 Sep 2026 21:17:01 +0300 Subject: [PATCH 13/29] add HttpRequest::timeoutMs --- src/network/Curl.cpp | 3 +++ src/network/commons.hpp | 1 + 2 files changed, 4 insertions(+) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 42d4817c8..fd1b0ad14 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -82,6 +82,9 @@ public: curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &entry->buffer); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0"); + if (request.timeoutMs > 0) { + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, request.timeoutMs); + } #ifndef NDEBUG // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); #endif diff --git a/src/network/commons.hpp b/src/network/commons.hpp index 46745bfbb..79e4bdb9d 100644 --- a/src/network/commons.hpp +++ b/src/network/commons.hpp @@ -28,6 +28,7 @@ namespace network { bool followLocation = false; bool verifySSL = true; long maxSize = -1; + long timeoutMs = 0; }; class Requests { From fc877f09cbe4338fa71086dd263cc8ad475487b0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 11 Sep 2026 21:19:13 +0300 Subject: [PATCH 14/29] add 'timeout_ms' to network.request --- src/logic/scripting/lua/libs/libnetwork.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index d8c9e7c98..577eee7a9 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -109,6 +109,10 @@ static int l_request(lua::State* L, network::Network& network) { request.followLocation = lua::toboolean(L, -1); lua::pop(L); } + if (lua::getfield(L, "timeout_ms", 2)) { + request.timeoutMs = lua::tointeger(L, -1); + lua::pop(L); + } int currentRequestId = request_id++; request.onResponse = [currentRequestId](std::vector bytes) { From 58c974a3a1fe6acfbc44ae824aa258517f839656 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Sep 2026 16:16:59 +0300 Subject: [PATCH 15/29] add HttpResponse --- src/logic/scripting/lua/libs/libnetwork.cpp | 16 ++- src/network/Curl.cpp | 136 ++++++++++++-------- src/network/commons.hpp | 12 +- test/network/curltest.cpp | 7 +- 4 files changed, 108 insertions(+), 63 deletions(-) diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 577eee7a9..37ef3029d 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -115,16 +115,24 @@ static int l_request(lua::State* L, network::Network& network) { } int currentRequestId = request_id++; - request.onResponse = [currentRequestId](std::vector bytes) { + request.onResponse = [currentRequestId](network::HttpResponse response) { push_event(NetworkEvent( RESPONSE, - ResponseEventDto {200, false, currentRequestId, std::move(bytes)} + ResponseEventDto { + response.status, + false, + currentRequestId, + std::move(response.body)} )); }; - request.onReject = [currentRequestId](int code, std::vector bytes) { + request.onReject = [currentRequestId](network::HttpResponse response) { push_event(NetworkEvent( RESPONSE, - ResponseEventDto {code, false, currentRequestId, std::move(bytes)} + ResponseEventDto { + response.status, + false, + currentRequestId, + std::move(response.body)} )); }; diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index fd1b0ad14..afa4be009 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -23,11 +23,28 @@ static size_t write_callback( return size * nmemb; } +static size_t header_callback( + char* buffer, size_t size, size_t nitems, void* userdata +) { + auto* headers = static_cast*>(userdata); + size_t len = size * nitems; + std::string header(buffer, len); + + while (!header.empty() && + (header.back() == '\r' || header.back() == '\n')) { + header.pop_back(); + } + + headers->push_back(std::move(header)); + return len; +} + struct ProcessingRequest { CURLM* multiHandle; CURL* curl; HttpRequest request; std::vector buffer; + std::vector headers; ProcessingRequest(CURLM* multiHandle) : multiHandle(multiHandle) { curl = curl_easy_init(); @@ -81,6 +98,8 @@ public: curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &entry->buffer); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &entry->headers); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0"); if (request.timeoutMs > 0) { curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, request.timeoutMs); @@ -102,7 +121,7 @@ public: auto message = curl_multi_strerror(res); logger.error() << message << " (" << request.url << ")"; if (request.onReject) { - request.onReject(HTTP_BAD_GATEWAY, {}); + request.onReject({HTTP_BAD_GATEWAY, {}, {}}); } } entry->request = std::move(request); @@ -112,69 +131,78 @@ public: void update() override { int messagesLeft; int running; - CURLMsg* msg; CURLMcode res = curl_multi_perform(multiHandle, &running); if (res != CURLM_OK) { auto message = curl_multi_strerror(res); logger.error() << message; return; } - if ((msg = curl_multi_info_read(multiHandle, &messagesLeft)) != nullptr) { - auto curl = msg->easy_handle; - auto found = std::find_if( - requests.begin(), requests.end(), - [curl](const std::unique_ptr& entry) { - return entry->curl == curl; - } - ); - if (found == requests.end()) { - logger.error() << "could not find request for cURL handle"; - return; + CURLMsg* msg = curl_multi_info_read(multiHandle, &messagesLeft); + if (msg == nullptr) { + return; + } + auto curl = msg->easy_handle; + auto found = std::find_if( + requests.begin(), + requests.end(), + [curl](const std::unique_ptr& entry) { + return entry->curl == curl; } - auto entry = std::move(*found); - auto& req = entry->request; + ); + if (found == requests.end()) { + logger.error() << "could not find request for cURL handle"; + return; + } + auto entry = std::move(*found); + auto& req = entry->request; - requests.erase(found); + requests.erase(found); - if(msg->msg == CURLMSG_DONE) { - curl_multi_remove_handle(multiHandle, curl); + if (msg->msg == CURLMSG_DONE) { + curl_multi_remove_handle(multiHandle, curl); + } + int response = -1; + CURLcode result = msg->data.result; + curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response); + auto headers = std::move(entry->headers); + if (response == HTTP_OK) { + long size; + if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) { + totalUpload += size; } - int response = -1; - CURLcode result = msg->data.result; - curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response); - if (response == HTTP_OK) { - long size; - if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) { - totalUpload += size; - } - if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) { - totalDownload += size; - } - totalDownload += entry->buffer.size(); - if (req.onResponse) { - req.onResponse(std::move(entry->buffer)); - } - } else if (response == 0) { - auto message = std::string(curl_easy_strerror(result)); - logger.error() << message << " (" << req.url << ")"; - if (req.onReject) { - req.onReject( - response, - std::vector( - message.data(), message.data() + message.size() - ) - ); - } - } else { - logger.error() - << "response code " << response << " (" << req.url << ")" - << (entry->buffer.empty() - ? "" - : std::to_string(entry->buffer.size()) + " byte(s)"); - totalDownload += entry->buffer.size(); - if (req.onReject) { - req.onReject(response, std::move(entry->buffer)); - } + if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) { + totalDownload += size; + } + totalDownload += entry->buffer.size(); + if (req.onResponse) { + req.onResponse({ + response, + std::move(headers), + std::move(entry->buffer), + }); + } + } else if (response == 0) { + auto message = std::string(curl_easy_strerror(result)); + logger.error() << message << " (" << req.url << ")"; + if (req.onReject) { + req.onReject( + {response, + std::move(headers), + std::vector( + message.data(), message.data() + message.size() + )} + ); + } + } else { + logger.error() << "response code " << response << " (" << req.url + << ")" + << (entry->buffer.empty() + ? "" + : std::to_string(entry->buffer.size()) + + " byte(s)"); + totalDownload += entry->buffer.size(); + if (req.onReject) { + req.onReject({response, {}, std::move(entry->buffer)}); } } } diff --git a/src/network/commons.hpp b/src/network/commons.hpp index 79e4bdb9d..15fe69850 100644 --- a/src/network/commons.hpp +++ b/src/network/commons.hpp @@ -10,8 +10,10 @@ #include namespace network { - using OnResponse = std::function)>; - using OnReject = std::function)>; + struct HttpResponse; + + using OnResponse = std::function; + using OnReject = std::function; using ConnectCallback = std::function; using ConnectErrorCallback = std::function; using ServerDatagramCallback = std::function; @@ -31,6 +33,12 @@ namespace network { long timeoutMs = 0; }; + struct HttpResponse { + int status; + std::vector headers; + std::vector body; + }; + class Requests { public: virtual ~Requests() {} diff --git a/test/network/curltest.cpp b/test/network/curltest.cpp index 44f87d17e..e7fc4840d 100644 --- a/test/network/curltest.cpp +++ b/test/network/curltest.cpp @@ -10,11 +10,12 @@ TEST(curltest, curltest) { network::HttpRequest request {}; request.url = "https://raw.githubusercontent.com/MihailRis/VoxelEngine-Cpp/refs/" "heads/curl/res/content/base/blocks/lamp.json"; - request.onResponse = [](std::vector data) { - if (data.empty()) { + request.onResponse = [](network::HttpResponse response) { + if (response.body.empty()) { return; } - auto view = std::string_view(data.data(), data.size()); + auto view = + std::string_view(response.body.data(), response.body.size()); auto value = json::parse(view); std::cout << value << std::endl; }; From 432687c6abd24d77757b616ed6cfbd666fec31f8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Sep 2026 17:55:12 +0300 Subject: [PATCH 16/29] cleanup request --- src/logic/scripting/lua/libs/libnetwork.cpp | 10 ---- src/network/Curl.cpp | 59 ++++++++------------- src/network/commons.hpp | 2 - 3 files changed, 22 insertions(+), 49 deletions(-) diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 37ef3029d..475158dea 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -125,16 +125,6 @@ static int l_request(lua::State* L, network::Network& network) { std::move(response.body)} )); }; - request.onReject = [currentRequestId](network::HttpResponse response) { - push_event(NetworkEvent( - RESPONSE, - ResponseEventDto { - response.status, - false, - currentRequestId, - std::move(response.body)} - )); - }; network.request(std::move(request)); return lua::pushinteger(L, currentRequestId); diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index afa4be009..2acda8c8c 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -120,9 +120,10 @@ public: if (res != CURLM_OK) { auto message = curl_multi_strerror(res); logger.error() << message << " (" << request.url << ")"; - if (request.onReject) { - request.onReject({HTTP_BAD_GATEWAY, {}, {}}); + if (request.onResponse) { + request.onResponse({0, {}, {}}); } + return; } entry->request = std::move(request); requests.push_back(std::move(entry)); @@ -146,46 +147,25 @@ public: requests.begin(), requests.end(), [curl](const std::unique_ptr& entry) { - return entry->curl == curl; + return entry && entry->curl == curl; } ); - if (found == requests.end()) { + if (requests.empty() || found == requests.end()) { logger.error() << "could not find request for cURL handle"; return; } auto entry = std::move(*found); - auto& req = entry->request; - requests.erase(found); - if (msg->msg == CURLMSG_DONE) { - curl_multi_remove_handle(multiHandle, curl); - } int response = -1; CURLcode result = msg->data.result; curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response); auto headers = std::move(entry->headers); - if (response == HTTP_OK) { - long size; - if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) { - totalUpload += size; - } - if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) { - totalDownload += size; - } - totalDownload += entry->buffer.size(); - if (req.onResponse) { - req.onResponse({ - response, - std::move(headers), - std::move(entry->buffer), - }); - } - } else if (response == 0) { + if (response == 0) { auto message = std::string(curl_easy_strerror(result)); - logger.error() << message << " (" << req.url << ")"; - if (req.onReject) { - req.onReject( + logger.error() << message << " (" << entry->request.url << ")"; + if (entry->request.onResponse) { + entry->request.onResponse( {response, std::move(headers), std::vector( @@ -194,15 +174,20 @@ public: ); } } else { - logger.error() << "response code " << response << " (" << req.url - << ")" - << (entry->buffer.empty() - ? "" - : std::to_string(entry->buffer.size()) + - " byte(s)"); + long size; + if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) { + totalUpload += size; + } + if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) { + totalDownload += size; + } totalDownload += entry->buffer.size(); - if (req.onReject) { - req.onReject({response, {}, std::move(entry->buffer)}); + if (entry->request.onResponse) { + entry->request.onResponse({ + response, + std::move(headers), + std::move(entry->buffer), + }); } } } diff --git a/src/network/commons.hpp b/src/network/commons.hpp index 15fe69850..cd98fbe00 100644 --- a/src/network/commons.hpp +++ b/src/network/commons.hpp @@ -13,7 +13,6 @@ namespace network { struct HttpResponse; using OnResponse = std::function; - using OnReject = std::function; using ConnectCallback = std::function; using ConnectErrorCallback = std::function; using ServerDatagramCallback = std::function; @@ -26,7 +25,6 @@ namespace network { std::vector headers; OnResponse onResponse; - OnReject onReject; bool followLocation = false; bool verifySSL = true; long maxSize = -1; From bb11a198f565fe19d9d28890364de7a8ef6b20c1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Sep 2026 19:41:26 +0300 Subject: [PATCH 17/29] fix CurlRequests::update --- dev/tests/network_http.lua | 2 +- src/network/Curl.cpp | 56 +++++++++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/dev/tests/network_http.lua b/dev/tests/network_http.lua index 5861460de..12149de09 100644 --- a/dev/tests/network_http.lua +++ b/dev/tests/network_http.lua @@ -8,4 +8,4 @@ end, function (code) response_received = true end) -app.sleep_until(function () return response_received end, nil, 10) +app.sleep_until(function () return response_received end, nil, 5) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 2acda8c8c..6a7cebd1a 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -114,26 +114,49 @@ public: } else { curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, request.maxSize); } - curl_multi_add_handle(multiHandle, curl); - int running; - CURLMcode res = curl_multi_perform(multiHandle, &running); - if (res != CURLM_OK) { - auto message = curl_multi_strerror(res); - logger.error() << message << " (" << request.url << ")"; - if (request.onResponse) { - request.onResponse({0, {}, {}}); + { + CURLMcode res = curl_multi_add_handle(multiHandle, curl); + if (res != CURLM_OK) { + auto message = curl_multi_strerror(res); + logger.error() << message << " (" << request.url << ")"; + if (request.onResponse) { + request.onResponse({0, {}, {}}); + } + return; + } + } + { + int running; + CURLMcode res = curl_multi_perform(multiHandle, &running); + if (res != CURLM_OK) { + auto message = curl_multi_strerror(res); + logger.error() << message << " (" << request.url << ")"; + if (request.onResponse) { + request.onResponse({0, {}, {}}); + } + return; } - return; } entry->request = std::move(request); requests.push_back(std::move(entry)); } void update() override { + if (CURLMcode res = curl_multi_poll( + multiHandle, + nullptr, + 0, + 1000, + nullptr + )) { + auto message = curl_multi_strerror(res); + logger.error() << message; + return; + } + int messagesLeft; int running; - CURLMcode res = curl_multi_perform(multiHandle, &running); - if (res != CURLM_OK) { + if (CURLMcode res = curl_multi_perform(multiHandle, &running)) { auto message = curl_multi_strerror(res); logger.error() << message; return; @@ -155,6 +178,7 @@ public: return; } auto entry = std::move(*found); + auto& req = entry->request; requests.erase(found); int response = -1; @@ -163,9 +187,9 @@ public: auto headers = std::move(entry->headers); if (response == 0) { auto message = std::string(curl_easy_strerror(result)); - logger.error() << message << " (" << entry->request.url << ")"; - if (entry->request.onResponse) { - entry->request.onResponse( + logger.error() << message << " (" << req.url << ")"; + if (req.onResponse) { + req.onResponse( {response, std::move(headers), std::vector( @@ -182,8 +206,8 @@ public: totalDownload += size; } totalDownload += entry->buffer.size(); - if (entry->request.onResponse) { - entry->request.onResponse({ + if (req.onResponse) { + req.onResponse({ response, std::move(headers), std::move(entry->buffer), From 5a9146f127bffea196b34d35488a04842dbf614b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Sep 2026 20:02:08 +0300 Subject: [PATCH 18/29] update workflows --- .github/workflows/appimage.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/windows.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index f43254bd3..8d05f59b3 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -48,7 +48,7 @@ jobs: - name: Run tests run: ctest --test-dir build - name: Run engine tests - timeout-minutes: 1 + timeout-minutes: 5 run: | chmod +x build/VoxelEngine chmod +x AppDir/usr/bin/vctest diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c3f5156f9..2f131de72 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -45,7 +45,7 @@ jobs: run: ctest --output-on-failure --test-dir build - name: Run engine tests - timeout-minutes: 1 + timeout-minutes: 5 run: | chmod +x build/VoxelEngine chmod +x AppDir/usr/bin/vctest diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index f248b6ab6..ffcb0c318 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -38,7 +38,7 @@ jobs: - name: Run engine tests run: | build/vctest/RelWithDebInfo/vctest.exe -e build/RelWithDebInfo/VoxelEngine.exe -d dev/tests -u build - timeout-minutes: 1 + timeout-minutes: 5 - name: Package for Windows run: | mkdir packaged From 4cb97d48a788be5e661ad3b69dd5f3280e32c1e7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 13 Sep 2026 20:09:25 +0300 Subject: [PATCH 19/29] fix --- src/network/Curl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 6a7cebd1a..65dce2f00 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -146,7 +146,7 @@ public: multiHandle, nullptr, 0, - 1000, + 0, nullptr )) { auto message = curl_multi_strerror(res); From bd8d55f8bf0d56ff31c179cbda46776f3e5de0aa Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 03:01:32 +0300 Subject: [PATCH 20/29] fix --- .github/workflows/macos.yml | 6 +++--- src/network/Curl.cpp | 8 ++++---- vctest/main.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 2f131de72..2bcb56dda 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -23,13 +23,13 @@ jobs: - name: Install dependencies from brew run: | - brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm freetype + brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm freetype - name: Configure run: | cmake -DOPENAL_INCLUDE_DIR=$(brew --prefix openal-soft)/include \ -DOPENAL_LIBRARY=$(brew --prefix openal-soft)/lib/libopenal.dylib \ - -S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}" + -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}" - name: Build run: | @@ -49,7 +49,7 @@ jobs: run: | chmod +x build/VoxelEngine chmod +x AppDir/usr/bin/vctest - AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always + AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always --memchecker "lldb -batch -o 'run' -o 'thread backtrace all' -o 'quit' -- " - name: Create DMG run: | mkdir VoxelEngineDmgContent diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 65dce2f00..66ffe170a 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -126,7 +126,7 @@ public: } } { - int running; + int running; CURLMcode res = curl_multi_perform(multiHandle, &running); if (res != CURLM_OK) { auto message = curl_multi_strerror(res); @@ -181,7 +181,7 @@ public: auto& req = entry->request; requests.erase(found); - int response = -1; + long response = -1; CURLcode result = msg->data.result; curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response); auto headers = std::move(entry->headers); @@ -190,7 +190,7 @@ public: logger.error() << message << " (" << req.url << ")"; if (req.onResponse) { req.onResponse( - {response, + {static_cast(response), std::move(headers), std::vector( message.data(), message.data() + message.size() @@ -208,7 +208,7 @@ public: totalDownload += entry->buffer.size(); if (req.onResponse) { req.onResponse({ - response, + static_cast(response), std::move(headers), std::move(entry->buffer), }); diff --git a/vctest/main.cpp b/vctest/main.cpp index ee2f15e5b..953d59aa9 100644 --- a/vctest/main.cpp +++ b/vctest/main.cpp @@ -183,8 +183,8 @@ static bool run_test(const Config& config, const fs::path& path, bool memcheck = auto name = path.stem(); std::stringstream ss; if (memcheck) { - ss << config.memchecker << " --log-file=" - << fix_path(memcheckLogFile.string()) << " "; + ss << config.memchecker << /*" --log-file=" + << fix_path(memcheckLogFile.string()) <<*/ " "; } ss << fs::canonical(config.executable) << " --headless"; ss << " --test " << fix_path(path.string()); From 5f37118f747e4061645675cf4aacbd9b97ccf3dd Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 03:05:40 +0300 Subject: [PATCH 21/29] fix other issues --- src/network/Curl.cpp | 52 +++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/network/Curl.cpp b/src/network/Curl.cpp index 66ffe170a..1c6507d36 100644 --- a/src/network/Curl.cpp +++ b/src/network/Curl.cpp @@ -4,7 +4,6 @@ #define NOMINMAX #include -#include using namespace network; @@ -50,9 +49,41 @@ struct ProcessingRequest { curl = curl_easy_init(); } + ProcessingRequest(const ProcessingRequest&) = delete; + ProcessingRequest& operator=(const ProcessingRequest&) = delete; + + ProcessingRequest(ProcessingRequest&& other) noexcept + : multiHandle(other.multiHandle), + curl(other.curl), + request(std::move(other.request)), + buffer(std::move(other.buffer)), + headers(std::move(other.headers)) { + other.curl = nullptr; + other.multiHandle = nullptr; + } + + ProcessingRequest& operator=(ProcessingRequest&& other) noexcept { + if (this != &other) { + if (curl) { + curl_multi_remove_handle(multiHandle, curl); + curl_easy_cleanup(curl); + } + multiHandle = other.multiHandle; + curl = other.curl; + request = std::move(other.request); + buffer = std::move(other.buffer); + headers = std::move(other.headers); + other.curl = nullptr; + other.multiHandle = nullptr; + } + return *this; + } + ~ProcessingRequest() { - curl_multi_remove_handle(multiHandle, curl); - curl_easy_cleanup(curl); + if (curl) { + curl_multi_remove_handle(multiHandle, curl); + curl_easy_cleanup(curl); + } } }; @@ -88,14 +119,14 @@ public: hs = curl_slist_append(hs, header.c_str()); } - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.body.length()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(request.body.length())); if (!request.body.empty()) { curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.body.data()); } - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, request.verifySSL); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, request.verifySSL); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, static_cast(request.verifySSL)); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, static_cast(request.verifySSL)); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, static_cast(request.followLocation)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &entry->buffer); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); @@ -162,7 +193,7 @@ public: return; } CURLMsg* msg = curl_multi_info_read(multiHandle, &messagesLeft); - if (msg == nullptr) { + if (msg == nullptr || msg->msg != CURLMSG_DONE) { return; } auto curl = msg->easy_handle; @@ -225,13 +256,8 @@ public: } static std::unique_ptr create() { - auto curl = curl_easy_init(); - if (curl == nullptr) { - throw std::runtime_error("could not initialize cURL"); - } auto multiHandle = curl_multi_init(); if (multiHandle == nullptr) { - curl_easy_cleanup(curl); throw std::runtime_error("could not initialize cURL-multi"); } return std::make_unique(multiHandle); From ef3a5cf278393781aab693d5036a3f4f9b299b44 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 12:41:12 +0300 Subject: [PATCH 22/29] feat: http response as table --- res/scripts/classes.lua | 24 +++++++++++++----- src/logic/scripting/lua/libs/libnetwork.cpp | 28 ++++++++++++++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index b044aa478..a6348f23a 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -127,8 +127,12 @@ network.get = function(url, callback, errorCallback, headers) return request(url, { method = "GET", headers = headers, - on_response = callback, - on_error = errorCallback, + on_response = function(response) + callback(response.body) + end, + on_error = function(response) + errorCallback(response.status, response.body) + end, follow_location = true, }) end @@ -137,8 +141,12 @@ network.get_binary = function(url, callback, errorCallback, headers) return request(url, { method = "GET", headers = headers, - on_response = callback and (function (response) return callback(Bytearray(response)) end), - on_error = errorCallback, + on_response = callback and (function (response) + return callback(Bytearray(response.body)) + end), + on_error = function(response) + errorCallback(response.status, response.body) + end, follow_location = true, }) end @@ -150,8 +158,12 @@ network.post = function(url, body, callback, errorCallback, headers) "Content-Type: application/json" }, headers), body = body, - on_response = callback, - on_error = errorCallback, + on_response = function(response) + callback(response.body) + end, + on_error = function(response) + errorCallback(response.status, response.body) + end, }) end diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 475158dea..2ccf25546 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -28,7 +28,7 @@ struct ResponseEventDto { int status; bool binary; int requestId; - std::vector bytes; + network::HttpResponse response; }; enum NetworkDatagramSide { @@ -122,7 +122,7 @@ static int l_request(lua::State* L, network::Network& network) { response.status, false, currentRequestId, - std::move(response.body)} + std::move(response)} )); }; @@ -493,11 +493,31 @@ static int l_pull_events(lua::State* L) { lua::pushinteger(L, dto.requestId); lua::rawseti(L, 3); + lua::createtable(L, 0, 2); + lua::pushinteger(L, dto.response.status); + lua::setfield(L, "status"); + if (dto.binary) { - lua::create_bytearray(L, dto.bytes.data(), dto.bytes.size()); + lua::create_bytearray( + L, dto.response.body.data(), dto.response.body.size() + ); } else { - lua::pushlstring(L, std::string_view(dto.bytes.data(), dto.bytes.size())); + lua::pushlstring( + L, + std::string_view( + dto.response.body.data(), dto.response.body.size() + ) + ); } + lua::setfield(L, "body"); + + lua::createtable(L, dto.response.headers.size(), 0); + for (int i = 0; i < dto.response.headers.size(); i++) { + lua::pushlstring(L, dto.response.headers[i]); + lua::rawseti(L, i + 1); + } + lua::setfield(L, "headers"); + lua::rawseti(L, 4); break; } From afc8ff2a0fca8718fcdfb0e1681dfd3cb088de4a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 18:36:18 +0300 Subject: [PATCH 23/29] fix http error event --- res/scripts/classes.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index a6348f23a..8c116139d 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -288,7 +288,7 @@ network.__process_events = function() _http_response_callbacks[event[3]] = nil _http_error_callbacks[event[3]] = nil if callback then - callback(event[2], event[4]) + callback(event[4]) end end end From 762634ea0494c3b8b0a9b8137f22215c7b7aeb69 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 19:44:41 +0300 Subject: [PATCH 24/29] add verify_ssl parameter --- src/logic/scripting/lua/libs/libnetwork.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index 2ccf25546..fbd722b1d 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -113,6 +113,10 @@ static int l_request(lua::State* L, network::Network& network) { request.timeoutMs = lua::tointeger(L, -1); lua::pop(L); } + if (lua::getfield(L, "verify_ssl", 2)) { + request.verifySSL = lua::toboolean(L, -1); + lua::pop(L); + } int currentRequestId = request_id++; request.onResponse = [currentRequestId](network::HttpResponse response) { From dad67db83500fcc373373936a1fafa26fdc78f3a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 19:51:05 +0300 Subject: [PATCH 25/29] feat: Bytearray request body support --- src/logic/scripting/lua/libs/libnetwork.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/logic/scripting/lua/libs/libnetwork.cpp b/src/logic/scripting/lua/libs/libnetwork.cpp index fbd722b1d..59d50f075 100644 --- a/src/logic/scripting/lua/libs/libnetwork.cpp +++ b/src/logic/scripting/lua/libs/libnetwork.cpp @@ -102,7 +102,11 @@ static int l_request(lua::State* L, network::Network& network) { lua::pop(L); } if (lua::getfield(L, "body", 2)) { - request.body = lua::require_lstring(L, -1); + if (lua::type(L, -1) == LUA_TCDATA) { + request.body = lua::bytearray_as_string(L, -1); + } else { + request.body = lua::require_lstring(L, -1); + } lua::pop(L); } if (lua::getfield(L, "follow_location", 2)) { From a773dfbd0e5da30411739e63b0ee0f60585dd558 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 19:56:34 +0300 Subject: [PATCH 26/29] remove _http_error_callbacks --- res/scripts/classes.lua | 47 ++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 8c116139d..4a8c8915b 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -98,7 +98,6 @@ local _udp_server_callbacks = {} local _udp_client_datagram_callbacks = {} local _udp_client_open_callbacks = {} local _http_response_callbacks = {} -local _http_error_callbacks = {} local http_request = network.__request local open_tcp = network.__open_tcp @@ -116,9 +115,6 @@ local function request(url, params) if params.on_response then _http_response_callbacks[id] = params.on_response end - if params.on_error then - _http_error_callbacks[id] = params.on_error - end end network.request = request @@ -128,7 +124,11 @@ network.get = function(url, callback, errorCallback, headers) method = "GET", headers = headers, on_response = function(response) - callback(response.body) + if response.status / 100 == 2 then + return callback(response.body) + else + return errorCallback(response.status, response.body) + end end, on_error = function(response) errorCallback(response.status, response.body) @@ -142,11 +142,12 @@ network.get_binary = function(url, callback, errorCallback, headers) method = "GET", headers = headers, on_response = callback and (function (response) - return callback(Bytearray(response.body)) + if response.code / 100 == 2 then + return callback(Bytearray(response.body)) + else + return errorCallback(response.status, response.body) + end end), - on_error = function(response) - errorCallback(response.status, response.body) - end, follow_location = true, }) end @@ -159,11 +160,13 @@ network.post = function(url, body, callback, errorCallback, headers) }, headers), body = body, on_response = function(response) - callback(response.body) - end, - on_error = function(response) - errorCallback(response.status, response.body) + if response.code / 100 == 2 then + return callback(Bytearray(response.body)) + else + return errorCallback(response.status, response.body) + end end, + follow_location = true, }) end @@ -276,20 +279,10 @@ network.__process_events = function() end end elseif etype == RESPONSE then - if event[2] / 100 == 2 then - local callback = _http_response_callbacks[event[3]] - _http_response_callbacks[event[3]] = nil - _http_error_callbacks[event[3]] = nil - if callback then - callback(event[4]) - end - else - local callback = _http_error_callbacks[event[3]] - _http_response_callbacks[event[3]] = nil - _http_error_callbacks[event[3]] = nil - if callback then - callback(event[4]) - end + local callback = _http_response_callbacks[event[3]] + _http_response_callbacks[event[3]] = nil + if callback then + callback(event[4]) end end From 817092fef45f663adba6dff48a65aaaaf87eaa8e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 20:06:09 +0300 Subject: [PATCH 27/29] update doc/*/scripting/builtins/libnetwork.md --- doc/en/scripting/builtins/libnetwork.md | 33 +++++++++++++++++++++++ doc/ru/scripting/builtins/libnetwork.md | 35 ++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/builtins/libnetwork.md b/doc/en/scripting/builtins/libnetwork.md index 0ce5c1b0e..f0a330c3a 100644 --- a/doc/en/scripting/builtins/libnetwork.md +++ b/doc/en/scripting/builtins/libnetwork.md @@ -4,6 +4,39 @@ A library for working with the network. ## HTTP requests +There is a configurable function `network.request` that allows performing HTTP requests with various methods (GET, POST, PUT, DELETE, etc.) and configuring headers, request body, timeout, and other parameters. + +>[!NOTE] To pass binary data in the request body, use a byte array (Bytearray) or a string. In `on_response`, the `body` string can be converted to a byte array using `Bytearray(response.body)`. + +```lua +network.request( + url: string, + parameters: { + -- Request method (GET, POST, PUT, DELETE, etc.) + method: string, + -- Request body as a string or Bytearray + body: table|string, + -- List of additional request headers + headers: table, + -- Timeout in milliseconds + timeout: int, + -- Whether to verify the SSL certificate + verify_ssl: boolean, + -- Function called when a response is received + on_response: function(response: { + -- HTTP response status code + status: int, + -- Response body as a string + body: string, + -- List of response headers + headers: table + }), + } +) +``` + +### Simplified functions for GET and POST requests + ```lua -- Performs a GET request to the specified URL. network.get( diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index 98c4e51e0..c76a056a4 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -4,6 +4,39 @@ ## HTTP-Запросы +Существует настраиваемая функция `network.request`, которая позволяет выполнять HTTP-запросы с различными методами (GET, POST, PUT, DELETE и т.д.) и настраивать заголовки, тело запроса, таймаут и другие параметры. + +>[!NOTE] Для передачи двоичных данных в теле запроса, используйте массив байт (Bytearray) или строку. В `on_response` строку `body` можно преобразовать в массив байт с помощью `Bytearray(response.body)`. + +```lua +network.request( + url: string, + parameters: { + -- Метод запроса (GET, POST, PUT, DELETE и т.д.) + method: string, + -- Тело запроса в виде строки или массива байт (Bytearray) + body: table|string, + -- Список дополнительных заголовков запроса + headers: table, + -- Таймаут в миллисекундах + timeout: int, + -- Проверять ли SSL-сертификат + verify_ssl: boolean, + -- Функция, вызываемая при получении ответа + on_response: function(response: { + -- HTTP-код ответа + status: int, + -- Тело ответа в виде строки + body: string, + -- Список заголовков ответа + headers: table + }), + } +) +``` + +### Упрощённые функции для GET и POST запросов + ```lua -- Выполняет GET запрос к указанному URL. network.get( @@ -231,4 +264,4 @@ network.get_total_download() -> int ```lua -- Ищет свободный для использования порт. network.find_free_port() -> int | nil -``` \ No newline at end of file +``` From 38da5c89f532ec6aca6f70c460950da6ecc3b02d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 20:07:34 +0300 Subject: [PATCH 28/29] fix --- doc/en/scripting/builtins/libnetwork.md | 2 +- doc/ru/scripting/builtins/libnetwork.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/scripting/builtins/libnetwork.md b/doc/en/scripting/builtins/libnetwork.md index f0a330c3a..8893407d4 100644 --- a/doc/en/scripting/builtins/libnetwork.md +++ b/doc/en/scripting/builtins/libnetwork.md @@ -6,7 +6,7 @@ A library for working with the network. There is a configurable function `network.request` that allows performing HTTP requests with various methods (GET, POST, PUT, DELETE, etc.) and configuring headers, request body, timeout, and other parameters. ->[!NOTE] To pass binary data in the request body, use a byte array (Bytearray) or a string. In `on_response`, the `body` string can be converted to a byte array using `Bytearray(response.body)`. +> To pass binary data in the request body, use a byte array (Bytearray) or a string. In `on_response`, the `body` string can be converted to a byte array using `Bytearray(response.body)`. ```lua network.request( diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index c76a056a4..c60f0f178 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -6,7 +6,7 @@ Существует настраиваемая функция `network.request`, которая позволяет выполнять HTTP-запросы с различными методами (GET, POST, PUT, DELETE и т.д.) и настраивать заголовки, тело запроса, таймаут и другие параметры. ->[!NOTE] Для передачи двоичных данных в теле запроса, используйте массив байт (Bytearray) или строку. В `on_response` строку `body` можно преобразовать в массив байт с помощью `Bytearray(response.body)`. +> Для передачи двоичных данных в теле запроса, используйте массив байт (Bytearray) или строку. В `on_response` строку `body` можно преобразовать в массив байт с помощью `Bytearray(response.body)`. ```lua network.request( From 7958e82a25a853caab17757c844f8acd05b045e8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 14 Sep 2026 22:16:18 +0300 Subject: [PATCH 29/29] add '--debugger' argument to vctest --- .github/workflows/appimage.yml | 2 +- .github/workflows/macos.yml | 2 +- vctest/main.cpp | 23 +++++++++++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 8d05f59b3..0d7670934 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -52,7 +52,7 @@ jobs: run: | chmod +x build/VoxelEngine chmod +x AppDir/usr/bin/vctest - AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build + AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --memchecker valgrind - name: Build AppImage uses: AppImageCrafters/build-appimage-action@fe2205a4d6056be47051f7b1b3811106e9814910 env: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 2bcb56dda..b4d9f6328 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -49,7 +49,7 @@ jobs: run: | chmod +x build/VoxelEngine chmod +x AppDir/usr/bin/vctest - AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always --memchecker "lldb -batch -o 'run' -o 'thread backtrace all' -o 'quit' -- " + AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always --debugger "lldb -batch -o 'run' -o 'thread backtrace all' -o 'quit' -- " - name: Create DMG run: | mkdir VoxelEngineDmgContent diff --git a/vctest/main.cpp b/vctest/main.cpp index 953d59aa9..99eebaacd 100644 --- a/vctest/main.cpp +++ b/vctest/main.cpp @@ -16,7 +16,8 @@ struct Config { fs::path directory; fs::path resDir {"res"}; fs::path workingDir {"."}; - std::string memchecker = "valgrind"; + std::string memchecker = ""; + std::string debugger = ""; bool outputAlways = false; }; @@ -30,6 +31,7 @@ static bool perform_keyword( std::cout << " --tests , -d = tests directory path\n"; std::cout << " --res , -r = 'res' directory path\n"; std::cout << " --user , -u = user directory path\n"; + std::cout << " --debugger = debugger with run command\n"; std::cout << " --memchecker = path to valgrind\n"; std::cout << " --output-always = always show tests output\n"; std::cout << std::endl; @@ -44,6 +46,8 @@ static bool perform_keyword( config.workingDir = fs::path(reader.next()); } else if (keyword == "--output-always") { config.outputAlways = true; + } else if (keyword == "--debugger") { + config.debugger = reader.next(); } else if (keyword == "--memchecker") { config.memchecker = reader.next(); } else { @@ -172,7 +176,7 @@ static std::string fix_path(std::string s) { return s; } -static bool run_test(const Config& config, const fs::path& path, bool memcheck = false) { +static bool run_test(const Config& config, const fs::path& path, bool debugRun = false) { using std::chrono::duration_cast; using std::chrono::high_resolution_clock; using std::chrono::milliseconds; @@ -182,9 +186,16 @@ static bool run_test(const Config& config, const fs::path& path, bool memcheck = auto name = path.stem(); std::stringstream ss; - if (memcheck) { - ss << config.memchecker << /*" --log-file=" - << fix_path(memcheckLogFile.string()) <<*/ " "; + if (debugRun) { + if (!config.memchecker.empty()) { + ss << config.memchecker; + ss << " --log-file=" << fix_path(memcheckLogFile.string()); + ss << " "; + } + if (!config.debugger.empty()) { + ss << config.debugger; + ss << " "; + } } ss << fs::canonical(config.executable) << " --headless"; ss << " --test " << fix_path(path.string()); @@ -203,7 +214,7 @@ static bool run_test(const Config& config, const fs::path& path, bool memcheck = .count(); if (code) { - if (memcheck) { + if (debugRun) { // valgrind-specific output display_segfault_valgrind(memcheckLogFile, name, std::cerr); fs::remove(memcheckLogFile);