Merge pull request #10 from BeamMP/segfault-fix-1

PostHTTP rebuild, SegFault fix (fixes #8), good work.
This commit is contained in:
Anonymous275
2021-02-02 00:17:40 +02:00
committed by GitHub
5 changed files with 173 additions and 73 deletions

View File

@@ -31,7 +31,7 @@ find_package(Lua REQUIRED)
target_include_directories(BeamMP-Server PUBLIC ${LUA_INCLUDE_DIR}) target_include_directories(BeamMP-Server PUBLIC ${LUA_INCLUDE_DIR})
if (UNIX) if (UNIX)
target_link_libraries(BeamMP-Server z pthread stdc++fs ${Boost_LINK_DIRS} ${LUA_LIBRARIES} curl dl) target_link_libraries(BeamMP-Server z pthread stdc++fs ${Boost_LINK_DIRS} ${LUA_LIBRARIES} curl dl crypto ssl)
elseif (WIN32) elseif (WIN32)
include(FindLua) include(FindLua)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)

View File

@@ -7,5 +7,6 @@
/// ///
#pragma once #pragma once
#include <string> #include <string>
std::string HttpRequest(const std::string& IP, int port); #include <unordered_map>
std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json); std::string HttpRequest(const std::string& host, int port, const std::string& target);
std::string PostHTTP(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json);

View File

@@ -13,6 +13,7 @@
#include <future> #include <future>
#include <sstream> #include <sstream>
#include <thread> #include <thread>
#include <unordered_map>
void WebsocketInit(); void WebsocketInit();
std::string GetPlayers() { std::string GetPlayers() {
@@ -36,8 +37,8 @@ std::string GenerateCall() {
<< "&desc=" << ServerDesc; << "&desc=" << ServerDesc;
return Ret.str(); return Ret.str();
} }
std::string RunPromise(const std::string& IP, const std::string& R) { std::string RunPromise(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& R, const std::string& body) {
std::packaged_task<std::string()> task([&] { return PostHTTP(IP, R, false); }); std::packaged_task<std::string()> task([&] { DebugPrintTIDInternal("Heartbeat_POST"); return PostHTTP(host, target, R, body, false); });
std::future<std::string> f1 = task.get_future(); std::future<std::string> f1 = task.get_future();
std::thread t(std::move(task)); std::thread t(std::move(task));
t.detach(); t.detach();
@@ -50,20 +51,32 @@ std::string RunPromise(const std::string& IP, const std::string& R) {
[[noreturn]] void Heartbeat() { [[noreturn]] void Heartbeat() {
DebugPrintTID(); DebugPrintTID();
std::string R, T; std::string R;
std::string T;
// these are "hot-change" related variables
static std::string LastR = "";
static std::chrono::high_resolution_clock::time_point LastNormalUpdateTime = std::chrono::high_resolution_clock::now();
bool isAuth = false; bool isAuth = false;
while (true) { while (true) {
R = GenerateCall(); R = 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 (LastR == R && (Now - LastNormalUpdateTime) < std::chrono::seconds(30)) {
std::this_thread::sleep_for(std::chrono::seconds(5));
continue;
}
LastR = R;
LastNormalUpdateTime = Now;
if (!CustomIP.empty()) if (!CustomIP.empty())
R += "&ip=" + CustomIP; R += "&ip=" + CustomIP;
std::string link = "https://beammp.com/heartbeatv2"; T = RunPromise("beammp.com", "/heartbeatv2", {}, R);
T = RunPromise(link, R);
if (T.substr(0, 2) != "20") { if (T.substr(0, 2) != "20") {
//Backend system refused server startup! //Backend system refused server startup!
std::this_thread::sleep_for(std::chrono::seconds(10)); std::this_thread::sleep_for(std::chrono::seconds(5));
std::string Backup = "https://backup1.beammp.com/heartbeatv2"; T = RunPromise("backup1.beammp.com", "/heartbeatv2", {}, R);
T = RunPromise(Backup, R);
if (T.substr(0, 2) != "20") { if (T.substr(0, 2) != "20") {
warn("Backend system refused server! Server might not show in the public list"); warn("Backend system refused server! Server might not show in the public list");
} }
@@ -71,11 +84,12 @@ std::string RunPromise(const std::string& IP, const std::string& R) {
//Server Authenticated //Server Authenticated
if (!isAuth) { if (!isAuth) {
WebsocketInit(); WebsocketInit();
if (T.length() == 4)info(("Authenticated!")); if (T.length() == 4)
else info(("Resumed authenticated session!")); info(("Authenticated!"));
else
info(("Resumed authenticated session!"));
isAuth = true; isAuth = true;
} }
//std::this_thread::sleep_for(std::chrono::seconds(5));
} }
} }
void HBInit() { void HBInit() {

View File

@@ -20,8 +20,7 @@
std::string GetClientInfo(const std::string& PK) { std::string GetClientInfo(const std::string& PK) {
if (!PK.empty()) { if (!PK.empty()) {
return PostHTTP("https://auth.beammp.com/pkToUser", R"({"key":")" + PK + "\"}", true); return PostHTTP("auth.beammp.com", "/pkToUser", {}, R"({"key":")" + PK + "\"}", true);
;
} }
return ""; return "";
} }

View File

@@ -7,69 +7,155 @@
/// ///
#include "CustomAssert.h" #include "CustomAssert.h"
#include <curl/curl.h>
#include <iostream> #include <iostream>
class CurlManager{ #include <boost/asio/connect.hpp>
public: #include <boost/asio/ip/tcp.hpp>
CurlManager(){ #include <boost/asio/ssl/error.hpp>
curl = curl_easy_init(); #include <boost/asio/ssl/stream.hpp>
} #include <boost/beast/core.hpp>
~CurlManager(){ #include <boost/beast/http.hpp>
curl_easy_cleanup(curl); #include <boost/beast/ssl.hpp>
} #include <boost/beast/version.hpp>
inline CURL* Get(){ #include <boost/lexical_cast.hpp>
return curl; #include <cstdlib>
} #include <iostream>
private: #include <sstream>
CURL *curl; #include <string>
};
static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { namespace beast = boost::beast; // from <boost/beast.hpp>
((std::string*)userp)->append((char*)contents, size * nmemb); namespace http = beast::http; // from <boost/beast/http.hpp>
return size * nmemb; namespace net = boost::asio; // from <boost/asio.hpp>
} namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
std::string HttpRequest(const std::string& IP, int port) { using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
static thread_local CurlManager M;
std::string readBuffer; // UNUSED?!
CURL* curl = M.Get(); std::string HttpRequest(const std::string& host, int port, const std::string& target) {
CURLcode res; // FIXME: this is likely not very well written.
Assert(curl); // if it causes issues, yell at me and I'll fix it asap. - Lion
if (curl) { try {
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); net::io_context io;
curl_easy_setopt(curl, CURLOPT_PORT, port); tcp::resolver resolver(io);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); beast::tcp_stream stream(io);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); auto const results = resolver.resolve(host, std::to_string(port));
res = curl_easy_perform(curl); stream.connect(results);
if (res != CURLE_OK)
return "-1"; 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";
} }
return readBuffer;
} }
std::string PostHTTP(const std::string& IP, const std::string& Fields, bool json) { std::string PostHTTP(const std::string& host, const std::string& target, const std::unordered_map<std::string, std::string>& fields, const std::string& body, bool json) {
auto header = curl_slist { (char*)"Content-Type: application/json" }; try {
static thread_local CurlManager M; net::io_context io;
static std::mutex Lock;
std::scoped_lock Guard(Lock);
CURL* curl = M.Get();
CURLcode res;
std::string readBuffer;
Assert(curl); // The SSL context is required, and holds certificates
if (curl) { ssl::context ctx(ssl::context::tlsv13);
curl_easy_setopt(curl, CURLOPT_URL, IP.c_str());
if (json) ctx.set_verify_mode(ssl::verify_none);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size()); tcp::resolver resolver(io);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str()); beast::ssl_stream<beast::tcp_stream> stream(io, ctx);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); decltype(resolver)::results_type results;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); auto try_connect_with_protocol = [&](tcp protocol) {
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); try {
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5); results = resolver.resolve(protocol, host, std::to_string(443));
res = curl_easy_perform(curl); if (!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
if (res != CURLE_OK) boost::system::error_code ec { static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
return "-1"; // 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");
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, boost::lexical_cast<std::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";
} }
return readBuffer;
} }