add socketio, http post & get

This commit is contained in:
Lion Kortlepel
2021-02-16 12:37:55 +01:00
committed by Anonymous275
parent 4cda6e8bc3
commit ef5db013b3
12 changed files with 487 additions and 19 deletions

143
src/Http.cpp Normal file
View File

@@ -0,0 +1,143 @@
#include "Http.h"
#include "CustomAssert.h"
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
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
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);
http::request<http::string_body> req { http::verb::get, target, 11 /* http 1.1 */ };
req.set(http::field::host, host);
// tell the server what we are (boost beast)
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
http::write(stream, req);
// used for reading
beast::flat_buffer buffer;
http::response<http::string_body> response;
http::read(stream, buffer, response);
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
}
return result;
} catch (const std::exception& e) {
error(e.what());
return "-1";
}
}
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, bool json) {
try {
net::io_context io;
// The SSL context is required, and holds certificates
ssl::context ctx(ssl::context::tlsv13);
ctx.set_verify_mode(ssl::verify_none);
tcp::resolver resolver(io);
beast::ssl_stream<beast::tcp_stream> stream(io, ctx);
decltype(resolver)::results_type results;
auto try_connect_with_protocol = [&](tcp protocol) {
try {
results = resolver.resolve(protocol, host, std::to_string(443));
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
// throw boost::system::system_error { ec };
debug("POST " + host + target + " failed.");
return false;
}
beast::get_lowest_layer(stream).connect(results);
} catch (const boost::system::system_error&) {
return false;
}
return true;
};
//bool ok = try_connect_with_protocol(tcp::v6());
//if (!ok) {
//debug("IPv6 connect failed, trying IPv4");
bool ok = try_connect_with_protocol(tcp::v4());
if (!ok) {
error("failed to resolve or connect in POST " + host + target);
return "-1";
}
//}
stream.handshake(ssl::stream_base::client);
http::request<http::string_body> req { http::verb::post, target, 11 /* http 1.1 */ };
req.set(http::field::host, host);
if (!body.empty()) {
if (json) {
// FIXME: json is untested.
req.set(http::field::content_type, "application/json");
} else {
req.set(http::field::content_type, "application/x-www-form-urlencoded");
}
req.set(http::field::content_length, std::to_string(body.size()));
req.body() = body;
// info("body is " + body + " (" + req.body() + ")");
// info("content size is " + std::to_string(body.size()) + " (" + boost::lexical_cast<std::string>(body.size()) + ")");
}
for (const auto& pair : fields) {
// info("setting " + pair.first + " to " + pair.second);
req.set(pair.first, pair.second);
}
std::stringstream oss;
oss << req;
beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(5));
http::write(stream, req);
// used for reading
beast::flat_buffer buffer;
http::response<http::string_body> response;
http::read(stream, buffer, response);
std::stringstream result;
result << response;
beast::error_code ec;
stream.shutdown(ec);
// IGNORING ec
// 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) {
error(e.what());
return "-1";
}
}

109
src/SocketIO.cpp Normal file
View File

@@ -0,0 +1,109 @@
#include "SocketIO.h"
#include "Logger.h"
#include "Settings.h"
static std::unique_ptr<SocketIO> SocketIOInstance = std::make_unique<SocketIO>();
SocketIO& SocketIO::Get() {
return *SocketIOInstance;
}
SocketIO::SocketIO()
: _Thread([this] { ThreadMain(); }) {
_Client.socket("/")->on("Hello", [&](sio::event&) {
DebugPrintTIDInternal("Hello-handler");
info("Got 'Hello' from backend socket-io!");
});
_Client.connect("https://backend.beammp.com");
_Client.set_logs_quiet();
}
SocketIO::~SocketIO() {
_CloseThread.store(true);
_Thread.join();
}
static constexpr auto RoomNameFromEnum(SocketIORoom Room) {
switch (Room) {
case SocketIORoom::None:
return "";
case SocketIORoom::Console:
return "console";
case SocketIORoom::Info:
return "info";
case SocketIORoom::Player:
return "player";
case SocketIORoom::Stats:
return "stats";
default:
error("unreachable code reached (developer error)");
abort();
}
}
static constexpr auto EventNameFromEnum(SocketIOEvent Event) {
switch (Event) {
case SocketIOEvent::CPUUsage:
return "cpu usage";
case SocketIOEvent::MemoryUsage:
return "memory usage";
case SocketIOEvent::ConsoleOut:
return "console out";
case SocketIOEvent::NetworkUsage:
return "network usage";
case SocketIOEvent::PlayerList:
return "player list";
default:
error("unreachable code reached (developer error)");
abort();
}
}
void SocketIO::Emit(SocketIORoom Room, SocketIOEvent Event, const std::string& Data) {
if (!_Authenticated) {
debug("trying to emit a socket.io event when not yet authenticated");
return;
}
std::string RoomName = RoomNameFromEnum(Room);
std::string EventName = EventNameFromEnum(Event);
debug("emitting event \"" + EventName + "\" with data: \"" + Data + "\" in room \"/key/" + RoomName + "\"");
std::unique_lock Lock(_QueueMutex);
_Queue.push_back({ RoomName, EventName, Data });
debug("queue now has " + std::to_string(_Queue.size()) + " events");
}
void SocketIO::ThreadMain() {
bool FirstTime = true;
while (!_CloseThread.load()) {
if (_Authenticated && FirstTime) {
FirstTime = false;
DebugPrintTID();
}
bool empty = false;
{ // queue lock scope
std::unique_lock Lock(_QueueMutex);
empty = _Queue.empty();
} // end queue lock scope
if (empty) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
} else {
Event TheEvent;
{ // queue lock scope
std::unique_lock Lock(_QueueMutex);
TheEvent = _Queue.front();
_Queue.pop_front();
} // end queue lock scope
debug("sending \"" + TheEvent.Name + "\" event");
auto Room = "/" + TheEvent.Room;
_Client.socket("/")->emit(TheEvent.Name, TheEvent.Data);
debug("sent \"" + TheEvent.Name + "\" event");
}
}
std::cout << "closing " + std::string(__func__) << std::endl;
_Client.sync_close();
_Client.clear_con_listeners();
std::cout << "closed" << std::endl;
}

57
src/THeartbeatThread.cpp Normal file
View File

@@ -0,0 +1,57 @@
#include "THeartbeatThread.h"
#include "Http.h"
#include "SocketIO.h"
THeartbeatThread::THeartbeatThread() {
}
void THeartbeatThread::operator()() {
std::string Body;
std::string T;
// these are "hot-change" related variables
static std::string Last = "";
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
bool isAuth = false;
while (true) {
Body = GenerateCall();
// a hot-change occurs when a setting has changed, to update the backend of that change.
auto Now = std::chrono::high_resolution_clock::now();
if (Last == Body && (Now - LastNormalUpdateTime) < std::chrono::seconds(30)) {
std::this_thread::sleep_for(std::chrono::seconds(5));
continue;
}
Last = Body;
LastNormalUpdateTime = Now;
if (!Application::Settings.CustomIP.empty())
Body += "&ip=" + Application::Settings.CustomIP;
T = Http::POST("backend.beammp.com", "/heartbeat", {}, Body, false);
if (T.substr(0, 2) != "20") {
//Backend system refused server startup!
std::this_thread::sleep_for(std::chrono::milliseconds(500));
T = Http::POST("backend.beammp.com", "/heartbeat", {}, Body, false);
// 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");
debug("server returned \"" + T + "\"");
isAuth = false;
}
}
if (!isAuth) {
if (T == "2000") {
info(("Authenticated!"));
isAuth = true;
} else if (T == "200") {
info(("Resumed authenticated session!"));
isAuth = true;
}
}
SocketIO::Get().SetAuthenticated(isAuth);
}
}

26
src/TResourceManager.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include "TResourceManager.h"
#include <algorithm>
#include <filesystem>
namespace fs = std::filesystem;
TResourceManager::TResourceManager() {
std::string Path = Application::Settings.Resource + "/Client";
if (!fs::exists(Path))
fs::create_directory(Path);
for (const auto& entry : fs::directory_iterator(Path)) {
auto pos = entry.path().string().find(".zip");
if (pos != std::string::npos) {
if (entry.path().string().length() - pos == 4) {
mFileList += entry.path().string() + ";";
mFileSizes += std::to_string(uint64_t(fs::file_size(entry.path()))) + ";";
mMaxModSize += uint64_t(fs::file_size(entry.path()));
mModsLoaded++;
}
}
}
std::replace(mFileList.begin(), mFileList.end(), '\\', '/');
if (mModsLoaded)
info("Loaded " + std::to_string(mModsLoaded) + " Mods");
}

View File

@@ -4,6 +4,7 @@
#include "TConfig.h"
#include "TConsole.h"
#include "TLuaEngine.h"
#include "TResourceManager.h"
#include "TServer.h"
#include <atomic>
#include <functional>
@@ -44,6 +45,8 @@ int main(int argc, char** argv) {
TServer Server(argc, argv);
TConfig Config("Server.cfg");
TLuaEngine LuaEngine(Server);
TResourceManager ResourceManager;
THeartbeatThread Heartbeat;
// TODO: replace
bool Shutdown = false;