From 9a80d73f7cd4fbe4d3c1df06256931ecf0f3e43f Mon Sep 17 00:00:00 2001 From: mjkzy Date: Thu, 30 Jun 2022 12:15:49 -0500 Subject: [PATCH] use iw5-script's http methods --- src/client/component/http.cpp | 173 --------------------- src/client/component/http.hpp | 11 -- src/client/game/scripting/lua/context.cpp | 174 ++++++++++++++++++++-- src/common/utils/http.cpp | 66 ++++++-- src/common/utils/http.hpp | 12 +- 5 files changed, 229 insertions(+), 207 deletions(-) delete mode 100644 src/client/component/http.cpp delete mode 100644 src/client/component/http.hpp diff --git a/src/client/component/http.cpp b/src/client/component/http.cpp deleted file mode 100644 index c92335da..00000000 --- a/src/client/component/http.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include -#include "loader/component_loader.hpp" - -#include "game/structs.hpp" -#include "game/game.hpp" - -#include "http.hpp" -#include "console.hpp" -#include "scheduler.hpp" -#include "scripting.hpp" - -#include -#include - -namespace http -{ - std::unordered_map active_requests{}; - uint64_t request_id{}; - - unsigned int http_get(const std::string& url) - { - const auto id = request_id++; - active_requests[id] = true; - - const auto object = scripting::array{}; - const auto object_id = object.get_entity_id(); - - scheduler::once([id, object_id, url]() - { - const auto data = utils::http::get_data(url); - scheduler::once([id, object_id, data]() - { - if (active_requests.find(id) == active_requests.end()) - { - return; - } - - if (!data.has_value()) - { - scripting::notify(object_id, "done", {{}, false, "Unknown error"}); - } - - const auto& result = data.value(); - const auto code = result.code; - const auto error = curl_easy_strerror(code); - - if (code != CURLE_OK) - { - scripting::notify(object_id, "done", {{}, false, error}); - } - - if (result.buffer.size() >= 0x5000) - { - console::warn("^3WARNING: HTTP result size bigger than 20480 bytes (%i), truncating!", static_cast(result.buffer.size())); - } - - scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true}); - }, scheduler::pipeline::server); - }, scheduler::pipeline::async); - - return object_id; - } - - unsigned int http_request(const std::string& url, const sol::variadic_args& va) - { - const auto id = request_id++; - active_requests[id] = true; - - const auto object = scripting::array{}; - const auto object_id = object.get_entity_id(); - - std::string fields_string{}; - std::unordered_map headers_map{}; - - if (va.size() > 0) - { - const scripting::array options = va[0].as(); - - const auto fields = options["parameters"]; - const auto body = options["body"]; - const auto headers = options["headers"]; - - if (fields.is()) - { - const auto fields_ = fields.as(); - const auto keys = fields_.get_keys(); - - for (const auto& key : keys) - { - if (!key.is()) - { - continue; - } - - const auto key_ = key.as(); - const auto value = fields_[key].to_string(); - fields_string += key_ + "=" + value + "&"; - } - } - - if (body.is()) - { - fields_string = body.as(); - } - - if (headers.is()) - { - const auto headers_ = headers.as(); - const auto keys = headers_.get_keys(); - - for (const auto& key : keys) - { - if (!key.is()) - { - continue; - } - - const auto key_ = key.as(); - const auto value = headers_[key].to_string(); - - headers_map[key_] = value; - } - } - } - - scheduler::once([id, object_id, url]() - { - const auto data = utils::http::get_data(url); - scheduler::once([id, object_id, data]() - { - if (active_requests.find(id) == active_requests.end()) - { - return; - } - - if (!data.has_value()) - { - scripting::notify(object_id, "done", {{}, false, "Unknown error"}); - } - - const auto& result = data.value(); - const auto code = result.code; - const auto error = curl_easy_strerror(code); - - if (code != CURLE_OK) - { - scripting::notify(object_id, "done", { {}, false, error }); - } - - if (result.buffer.size() >= 0x5000) - { - console::warn("^3WARNING: HTTP result size bigger than 20480 bytes (%i), truncating!", static_cast(result.buffer.size())); - } - - scripting::notify(object_id, "done", {result.buffer.substr(0, 0x5000), true}); - }, scheduler::pipeline::server); - }, scheduler::pipeline::async); - - return object_id; - } - - class component final : public component_interface - { - public: - void post_unpack() override - { - scripting::on_shutdown([]() - { - active_requests.clear(); - }); - } - }; -} diff --git a/src/client/component/http.hpp b/src/client/component/http.hpp deleted file mode 100644 index 19408787..00000000 --- a/src/client/component/http.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "game/scripting/lua/value_conversion.hpp" -#include "game/scripting/array.hpp" -#include "game/scripting/execution.hpp" - -namespace http -{ - unsigned int http_get(const std::string& url); - unsigned int http_request(const std::string& url, const sol::variadic_args& va); -} diff --git a/src/client/game/scripting/lua/context.cpp b/src/client/game/scripting/lua/context.cpp index e84cb59f..12dd2851 100644 --- a/src/client/game/scripting/lua/context.cpp +++ b/src/client/game/scripting/lua/context.cpp @@ -7,7 +7,6 @@ #include "../functions.hpp" #include "../../../component/command.hpp" -#include "../../../component/http.hpp" #include "../../../component/logfile.hpp" #include "../../../component/scripting.hpp" #include "../../../component/fastfiles.hpp" @@ -21,6 +20,21 @@ namespace scripting::lua { namespace { + struct http_request + { + sol::protected_function on_error; + sol::protected_function on_progress; + sol::protected_function on_load; + }; + + std::unordered_map http_requests; + + std::string get_as_string(const sol::this_state s, sol::object o) + { + sol::state_view state(s); + return state["tostring"](o); + } + vector normalize_vector(const vector& vec) { const auto length = sqrt( @@ -571,18 +585,160 @@ namespace scripting::lua } }; - game_type["httpget"] = [](const game& game, const sol::this_state s, - const std::string& url) + static uint64_t task_id = 0; + + state["http"] = sol::table::create(state.lua_state()); + + state["http"]["get"] = [](const sol::this_state, const std::string& url, + const sol::protected_function& callback, sol::variadic_args va) { - const auto request = http::http_get(url); - return convert(s, scripting::entity{request}); + bool async = false; + + if (va.size() >= 1 && va[0].get_type() == sol::type::boolean) + { + async = va[0].as(); + } + + const auto cur_task_id = task_id++; + auto request_callbacks = &http_requests[cur_task_id]; + request_callbacks->on_load = callback; + + ::scheduler::once([url, cur_task_id]() + { + if (http_requests.find(cur_task_id) == http_requests.end()) + { + return; + } + + const auto data = utils::http::get_data(url); + ::scheduler::once([data, cur_task_id]() + { + if (http_requests.find(cur_task_id) == http_requests.end()) + { + return; + } + + const auto& request_callbacks_ = http_requests[cur_task_id]; + const auto has_value = data.has_value(); + handle_error(request_callbacks_.on_load(has_value ? data.value().buffer : "", has_value)); + http_requests.erase(cur_task_id); + }, ::scheduler::pipeline::server); + }, ::scheduler::pipeline::async); }; - game_type["httprequest"] = [](const game& game, const sol::this_state s, - const std::string& url, sol::variadic_args va) + state["http"]["request"] = [](const sol::this_state s, const std::string& url, sol::variadic_args va) { - const auto request = http::http_request(url, va); - return convert(s, scripting::entity{request}); + auto request = sol::table::create(s.lua_state()); + + std::string buffer{}; + std::string fields_string{}; + std::unordered_map headers_map; + + if (va.size() >= 1 && va[0].get_type() == sol::type::table) + { + const auto options = va[0].as(); + + const auto fields = options["parameters"]; + const auto body = options["body"]; + const auto headers = options["headers"]; + + if (fields.get_type() == sol::type::table) + { + const auto _fields = fields.get(); + + for (const auto& field : _fields) + { + const auto key = field.first.as(); + const auto value = get_as_string(s, field.second); + + fields_string += key + "=" + value + "&"; + } + } + + if (body.get_type() == sol::type::string) + { + fields_string = body.get(); + } + + if (headers.get_type() == sol::type::table) + { + const auto _headers = headers.get(); + + for (const auto& header : _headers) + { + const auto key = header.first.as(); + const auto value = get_as_string(s, header.second); + + headers_map[key] = value; + } + } + } + + request["onerror"] = []() {}; + request["onprogress"] = []() {}; + request["onload"] = []() {}; + + request["send"] = [url, fields_string, request, headers_map]() + { + const auto cur_task_id = task_id++; + auto request_callbacks = &http_requests[cur_task_id]; + request_callbacks->on_error = request["onerror"]; + request_callbacks->on_progress = request["onprogress"]; + request_callbacks->on_load = request["onload"]; + + ::scheduler::once([url, fields_string, cur_task_id, headers_map]() + { + if (http_requests.find(cur_task_id) == http_requests.end()) + { + return; + } + + const auto result = utils::http::get_data(url, fields_string, headers_map, [cur_task_id](size_t value) + { + ::scheduler::once([cur_task_id, value]() + { + if (http_requests.find(cur_task_id) == http_requests.end()) + { + return; + } + + const auto& request_callbacks_ = http_requests[cur_task_id]; + handle_error(request_callbacks_.on_progress(value)); + }, ::scheduler::pipeline::server); + }); + + ::scheduler::once([cur_task_id, result]() + { + if (http_requests.find(cur_task_id) == http_requests.end()) + { + return; + } + + const auto& request_callbacks_ = http_requests[cur_task_id]; + + if (!result.has_value()) + { + request_callbacks_.on_error("Unknown", -1); + return; + } + + const auto& http_result = result.value(); + + if (http_result.code == CURLE_OK) + { + handle_error(request_callbacks_.on_load(http_result.buffer)); + } + else + { + handle_error(request_callbacks_.on_error(curl_easy_strerror(http_result.code), http_result.code)); + } + + http_requests.erase(cur_task_id); + }, ::scheduler::pipeline::server); + }, ::scheduler::pipeline::async); + }; + + return request; }; } } diff --git a/src/common/utils/http.cpp b/src/common/utils/http.cpp index bdb3da42..06e6ac77 100644 --- a/src/common/utils/http.cpp +++ b/src/common/utils/http.cpp @@ -6,6 +6,32 @@ namespace utils::http { namespace { + struct progress_helper + { + const std::function* callback{}; + std::exception_ptr exception{}; + }; + + int progress_callback(void* clientp, const curl_off_t /*dltotal*/, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/) + { + auto* helper = static_cast(clientp); + + try + { + if (*helper->callback) + { + (*helper->callback)(dlnow); + } + } + catch (...) + { + helper->exception = std::current_exception(); + return -1; + } + + return 0; + } + size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp) { auto* buffer = static_cast(userp); @@ -16,7 +42,8 @@ namespace utils::http } } - std::optional get_data(const std::string& url) + std::optional get_data(const std::string& url, const std::string& fields, + const headers& headers, const std::function& callback) { curl_slist* header_list = nullptr; auto* curl = curl_easy_init(); @@ -31,13 +58,28 @@ namespace utils::http curl_easy_cleanup(curl); }); + for (const auto& header : headers) + { + auto data = header.first + ": " + header.second; + header_list = curl_slist_append(header_list, data.data()); + } + std::string buffer{}; + progress_helper helper{}; + helper.callback = &callback; curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list); curl_easy_setopt(curl, CURLOPT_URL, url.data()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + + if (!fields.empty()) + { + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.data()); + } const auto code = curl_easy_perform(curl); @@ -49,20 +91,24 @@ namespace utils::http return result; } - else - { - result result; - result.code = code; - return result; + if (helper.exception) + { + std::rethrow_exception(helper.exception); } + + result result; + result.code = code; + + return result; } - std::future> get_data_async(const std::string& url) + std::future> get_data_async(const std::string& url, const std::string& fields, + const headers& headers, const std::function& callback) { - return std::async(std::launch::async, [url]() + return std::async(std::launch::async, [url, fields, headers, callback]() { - return get_data(url); + return get_data(url, fields, headers, callback); }); } } diff --git a/src/common/utils/http.hpp b/src/common/utils/http.hpp index 7361a7b2..bb4c90e5 100644 --- a/src/common/utils/http.hpp +++ b/src/common/utils/http.hpp @@ -11,10 +11,14 @@ namespace utils::http { struct result { - CURLcode code; - std::string buffer; + CURLcode code{}; + std::string buffer{}; }; - std::optional get_data(const std::string& url); - std::future> get_data_async(const std::string& url); + using headers = std::unordered_map; + + std::optional get_data(const std::string& url, const std::string& fields = {}, + const headers& headers = {}, const std::function& callback = {}); + std::future> get_data_async(const std::string& url, const std::string& fields = {}, + const headers& headers = {}, const std::function& callback = {}); }