add MP.HttpsGET, MP.HttpsPOST

This commit is contained in:
Lion Kortlepel
2021-07-01 00:33:28 +02:00
parent 8853cef809
commit 44e0f3aa21
5 changed files with 188 additions and 42 deletions

View File

@@ -4,6 +4,7 @@
#include <unordered_map>
namespace Http {
std::string GET(const std::string& host, int port, const std::string& target);
std::string POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType);
std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr);
std::string POST(const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr);
static inline const char* ErrorString = "!BeamMPHttpsError!";
}

View File

@@ -14,47 +14,85 @@ namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
std::string Http::GET(const std::string& host, int port, const std::string& target) {
// FIXME: doesn't support https
// if it causes issues, yell at me and I'll fix it asap. - Lion
std::string Http::GET(const std::string& host, int port, const std::string& target, unsigned int* status) {
try {
net::io_context io;
tcp::resolver resolver(io);
beast::tcp_stream stream(io);
auto const results = resolver.resolve(host, std::to_string(port));
stream.connect(results);
// Check command line arguments.
int version = 11;
http::request<http::string_body> req { http::verb::get, target, 11 /* http 1.1 */ };
// The io_context is required for all I/O
net::io_context ioc;
req.set(http::field::host, host);
// tell the server what we are (boost beast)
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// The SSL context is required, and holds certificates
ssl::context ctx(ssl::context::tlsv12_client);
http::write(stream, req);
// This holds the root certificate used for verification
// we don't do / have this
// load_root_certificates(ctx);
// used for reading
beast::flat_buffer buffer;
http::response<http::string_body> response;
// Verify the remote server's certificate
ctx.set_verify_mode(ssl::verify_none);
http::read(stream, buffer, response);
// These objects perform our I/O
tcp::resolver resolver(ioc);
beast::ssl_stream<beast::tcp_stream> stream(ioc, ctx);
std::string result(response.body());
beast::error_code ec;
stream.socket().shutdown(tcp::socket::shutdown_both, ec);
if (ec && ec != beast::errc::not_connected) {
throw beast::system_error { ec }; // goes down to `return "-1"` anyways
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
beast::error_code ec { static_cast<int>(::ERR_get_error()), net::error::get_ssl_category() };
throw beast::system_error { ec };
}
return result;
// Look up the domain name
auto const results = resolver.resolve(host.c_str(), std::to_string(port));
} catch (const std::exception& e) {
Application::Console().Write(e.what());
return "-1";
// Make the connection on the IP address we get from a lookup
beast::get_lowest_layer(stream).connect(results);
// Perform the SSL handshake
stream.handshake(ssl::stream_base::client);
// Set up an HTTP GET request message
http::request<http::string_body> req { http::verb::get, target, version };
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::write(stream, req);
// This buffer is used for reading and must be persisted
beast::flat_buffer buffer;
// Declare a container to hold the response
http::response<http::string_body> res;
// Receive the HTTP response
http::read(stream, buffer, res);
// Gracefully close the stream
beast::error_code ec;
stream.shutdown(ec);
if (ec == net::error::eof) {
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec = {};
}
if (status) {
*status = res.base().result_int();
}
if (ec)
throw beast::system_error { ec };
// If we get here then the connection is closed gracefully
return std::string(res.body());
} catch (std::exception const& e) {
Application::Console().Write(__func__ + std::string(": ") + e.what());
return ErrorString;
}
}
std::string Http::POST(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType) {
std::string Http::POST(const std::string& host, int port, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, const std::string& ContentType, unsigned int* status) {
try {
net::io_context io;
@@ -68,7 +106,7 @@ std::string Http::POST(const std::string& host, const std::string& target, const
decltype(resolver)::results_type results;
auto try_connect_with_protocol = [&](tcp protocol) {
try {
results = resolver.resolve(protocol, host, std::to_string(443));
results = resolver.resolve(protocol, host, std::to_string(port));
if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
boost::system::error_code ec { static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
// FIXME: we could throw and crash, if we like
@@ -121,6 +159,10 @@ std::string Http::POST(const std::string& host, const std::string& target, const
http::read(stream, buffer, response);
if (status) {
*status = response.base().result_int();
}
std::stringstream result;
result << response;
@@ -131,11 +173,12 @@ std::string Http::POST(const std::string& host, const std::string& target, const
// info(result.str());
std::string debug_response_str;
std::getline(result, debug_response_str);
//debug("POST " + host + target + ": " + debug_response_str);
return std::string(response.body());
} catch (const std::exception& e) {
Application::Console().Write(e.what());
return "-1";
Application::Console().Write(__func__ + std::string(": ") + e.what());
return ErrorString;
}
}

View File

@@ -35,12 +35,12 @@ void THeartbeatThread::operator()() {
Body += "&pps=" + Application::PPS();
T = Http::POST(Application::GetBackendHostname(), "/heartbeat", {}, Body, "application/x-www-form-urlencoded");
T = Http::POST(Application::GetBackendHostname(), 443, "/heartbeat", {}, Body, "application/x-www-form-urlencoded");
if (T.substr(0, 2) != "20") {
//Backend system refused server startup!
std::this_thread::sleep_for(std::chrono::milliseconds(500));
T = Http::POST(Application::GetBackendHostname(), "/heartbeat", {}, Body, "application/x-www-form-urlencoded");
T = Http::POST(Application::GetBackendHostname(), 443, "/heartbeat", {}, Body, "application/x-www-form-urlencoded");
// TODO backup2 + HTTP flag (no TSL)
if (T.substr(0, 2) != "20") {
warn("Backend system refused server! Server might not show in the public list");

View File

@@ -650,7 +650,7 @@ int lua_Registered(lua_State* L) {
lua_getstack(L, 0, &info);
lua_getinfo(L, "n", &info);
if(auto it = TLuaEngine::mGlobals.find(info.name); it != TLuaEngine::mGlobals.end()){
if (auto it = TLuaEngine::mGlobals.find(info.name); it != TLuaEngine::mGlobals.end()) {
lua_getglobal(it->second, info.name);
if (lua_isfunction(it->second, -1)) {
lua_pcall(it->second, 0, 0, 0); //TODO revisit to allow arguments and return also we need to mutex this
@@ -663,13 +663,13 @@ int lua_Registered(lua_State* L) {
}
int lua_Register(lua_State* L) {
if(lua_isstring(L, 1)){
if (lua_isstring(L, 1)) {
std::string Name(lua_tolstring(L, 1, nullptr));
lua_getglobal(L, Name.c_str());
if (lua_isfunction(L, -1)) {
TLuaEngine::mGlobals.emplace(Name, L);
for (auto& Script : Engine().LuaFiles()) {
if(Script->GetState() != L){
if (Script->GetState() != L) {
lua_register(Script->GetState(), Name.c_str(), lua_Registered);
}
}
@@ -679,7 +679,7 @@ int lua_Register(lua_State* L) {
ClearStack(L);
}
} else {
SendError(Engine(), L, "Register wrong arguments expected string");
SendError(Engine(), L, "Wrong arguments to `Register`, expected string");
}
return 0;
}
@@ -772,6 +772,106 @@ int lua_GetOSName(lua_State* L) {
return 1;
}
// status, body = HttpGET(host, port, target)
// example usage:
// send a GET https://example.com:443/index.html:
// status, body = MP.HttpGET("example.com", 443, "/index.html")
int lua_HttpsGET(lua_State* L) {
if (!lua_isstring(L, 1)) {
SendError(Engine(), L, "`HttpsGET` expects host (type string) as first argument.");
ClearStack(L);
return 0;
}
if (!lua_isnumber(L, 2)) {
SendError(Engine(), L, "`HttpsGET` expects port (type number) as second argument.");
ClearStack(L);
return 0;
}
if (!lua_isstring(L, 3)) {
SendError(Engine(), L, "`HttpsGET` expects target (type string) as third argument.");
ClearStack(L);
return 0;
}
auto Host = lua_tostring(L, 1);
auto Port = int(lua_tointeger(L, 2));
auto Target = lua_tostring(L, 3);
ClearStack(L);
unsigned int Status;
auto Body = Http::GET(Host, Port, Target, &Status);
lua_pushinteger(L, Status);
auto PrettyRemote = "https://" + std::string(Host) + ":" + std::to_string(Port) + std::string(Target);
if (Body == Http::ErrorString) {
SendError(Engine(), L, "HTTPS GET " + PrettyRemote + " failed status " + std::to_string(Status) + ". Check the console or log for more info.");
return 1;
} else {
debug("GET " + PrettyRemote + " completed status " + std::to_string(Status));
}
lua_pushstring(L, Body.c_str());
return 2;
}
// status, body = HttpsPOST(host, port, target, body, content_type)
int lua_HttpsPOST(lua_State* L) {
if (!lua_isstring(L, 1)) {
SendError(Engine(), L, "`HttpsPOST` expects host (type string) as 1. argument.");
ClearStack(L);
return 0;
}
if (!lua_isnumber(L, 2)) {
SendError(Engine(), L, "`HttpsPOST` expects port (type number) as 2. argument.");
ClearStack(L);
return 0;
}
if (!lua_isstring(L, 3)) {
SendError(Engine(), L, "`HttpsPOST` expects target (type string) as 3. argument.");
ClearStack(L);
return 0;
}
if (!lua_isstring(L, 4)) {
SendError(Engine(), L, "`HttpsPOST` expects body (type string) as 4. argument.");
ClearStack(L);
return 0;
}
if (!lua_isstring(L, 5)) {
SendError(Engine(), L, "`HttpsPOST` expects content_type (type string) as 5. argument.");
ClearStack(L);
return 0;
}
auto Host = lua_tostring(L, 1);
auto Port = int(lua_tointeger(L, 2));
auto Target = lua_tostring(L, 3);
auto RequestBody = lua_tostring(L, 4);
auto ContentType = lua_tostring(L, 5);
ClearStack(L);
// build fields
std::unordered_map<std::string, std::string> Fields;
unsigned int Status;
auto ResponseBody = Http::POST(Host, Port, Target, {}, RequestBody, ContentType, &Status);
lua_pushinteger(L, Status);
auto PrettyRemote = "https://" + std::string(Host) + ":" + std::to_string(Port) + std::string(Target);
if (ResponseBody == Http::ErrorString) {
SendError(Engine(), L, "HTTPS POST " + PrettyRemote + " failed status " + std::to_string(Status) + ". Check the console or log for more info.");
return 1;
} else {
debug("POST " + PrettyRemote + " completed status " + std::to_string(Status));
}
lua_pushstring(L, ResponseBody.c_str());
return 2;
}
void TLuaFile::Load() {
Assert(mLuaState);
luaL_openlibs(mLuaState);
@@ -799,6 +899,8 @@ void TLuaFile::Load() {
LuaTable::InsertFunction(mLuaState, "Sleep", lua_Sleep);
LuaTable::InsertFunction(mLuaState, "Set", lua_Set);
LuaTable::InsertFunction(mLuaState, "GetOSName", lua_GetOSName);
LuaTable::InsertFunction(mLuaState, "HttpsGET", lua_HttpsGET);
LuaTable::InsertFunction(mLuaState, "HttpsPOST", lua_HttpsPOST);
LuaTable::End(mLuaState, "MP");
lua_register(mLuaState, "print", lua_Print);
@@ -874,7 +976,7 @@ void SendError(TLuaEngine& Engine, lua_State* L, const std::string& msg) {
TLuaFile& S = MaybeS.value();
a = fs::path(S.GetFileName()).filename().string();
}
warn(a + (" | Incorrect Call of ") + msg);
warn(a + (" | Error in MP Lua call: ") + msg);
}
void TLuaArg::PushArgs(lua_State* State) {

View File

@@ -281,12 +281,12 @@ void TNetwork::Authentication(SOCKET TCPSock) {
}
if (!Rc.empty()) {
Rc = Http::POST(Application::GetBackendUrlForAuth(), "/pkToUser", {}, R"({"key":")" + Rc + "\"}", "application/json");
Rc = Http::POST(Application::GetBackendUrlForAuth(), 443, "/pkToUser", {}, R"({"key":")" + Rc + "\"}", "application/json");
}
json::Document AuthResponse;
AuthResponse.Parse(Rc.c_str());
if (Rc == "-1" || AuthResponse.HasParseError()) {
if (Rc == Http::ErrorString || AuthResponse.HasParseError()) {
ClientKick(*Client, "Invalid key! Please restart your game.");
return;
}