use iw5-script's http methods

This commit is contained in:
mjkzy 2022-06-30 12:15:49 -05:00
parent 47942bab95
commit 9a80d73f7c
5 changed files with 229 additions and 207 deletions

View File

@ -1,173 +0,0 @@
#include <std_include.hpp>
#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 <utils/http.hpp>
#include <curl/curl.h>
namespace http
{
std::unordered_map<uint64_t, bool> 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<int>(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<std::string, std::string> headers_map{};
if (va.size() > 0)
{
const scripting::array options = va[0].as<scripting::array>();
const auto fields = options["parameters"];
const auto body = options["body"];
const auto headers = options["headers"];
if (fields.is<scripting::array>())
{
const auto fields_ = fields.as<scripting::array>();
const auto keys = fields_.get_keys();
for (const auto& key : keys)
{
if (!key.is<std::string>())
{
continue;
}
const auto key_ = key.as<std::string>();
const auto value = fields_[key].to_string();
fields_string += key_ + "=" + value + "&";
}
}
if (body.is<std::string>())
{
fields_string = body.as<std::string>();
}
if (headers.is<scripting::array>())
{
const auto headers_ = headers.as<scripting::array>();
const auto keys = headers_.get_keys();
for (const auto& key : keys)
{
if (!key.is<std::string>())
{
continue;
}
const auto key_ = key.as<std::string>();
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<int>(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();
});
}
};
}

View File

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

View File

@ -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<uint64_t, http_request> 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<bool>();
}
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<std::string, std::string> headers_map;
if (va.size() >= 1 && va[0].get_type() == sol::type::table)
{
const auto options = va[0].as<sol::table>();
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<sol::table>();
for (const auto& field : _fields)
{
const auto key = field.first.as<std::string>();
const auto value = get_as_string(s, field.second);
fields_string += key + "=" + value + "&";
}
}
if (body.get_type() == sol::type::string)
{
fields_string = body.get<std::string>();
}
if (headers.get_type() == sol::type::table)
{
const auto _headers = headers.get<sol::table>();
for (const auto& header : _headers)
{
const auto key = header.first.as<std::string>();
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;
};
}
}

View File

@ -6,6 +6,32 @@ namespace utils::http
{
namespace
{
struct progress_helper
{
const std::function<void(size_t)>* 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<progress_helper*>(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<std::string*>(userp);
@ -16,7 +42,8 @@ namespace utils::http
}
}
std::optional<result> get_data(const std::string& url)
std::optional<result> get_data(const std::string& url, const std::string& fields,
const headers& headers, const std::function<void(size_t)>& 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
if (helper.exception)
{
std::rethrow_exception(helper.exception);
}
result result;
result.code = code;
return result;
}
}
std::future<std::optional<result>> get_data_async(const std::string& url)
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields,
const headers& headers, const std::function<void(size_t)>& 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);
});
}
}

View File

@ -11,10 +11,14 @@ namespace utils::http
{
struct result
{
CURLcode code;
std::string buffer;
CURLcode code{};
std::string buffer{};
};
std::optional<result> get_data(const std::string& url);
std::future<std::optional<result>> get_data_async(const std::string& url);
using headers = std::unordered_map<std::string, std::string>;
std::optional<result> get_data(const std::string& url, const std::string& fields = {},
const headers& headers = {}, const std::function<void(size_t)>& callback = {});
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields = {},
const headers& headers = {}, const std::function<void(size_t)>& callback = {});
}