#pragma once // ------------------------------------------------------------------------------------------------ #include "Core/Utility.hpp" #include "Misc/Functions.hpp" // ------------------------------------------------------------------------------------------------ #include #include #include #include #include #include #include #include #include // ------------------------------------------------------------------------------------------------ #include "Poco/Net/HTTPClientSession.h" #include "Poco/Net/HTTPRequest.h" #include "Poco/Net/HTTPResponse.h" #include "Poco/Net/HTTPCredentials.h" #include "Poco/StreamCopier.h" #include "Poco/NullStream.h" #include "Poco/Exception.h" #include "Poco/Path.h" #include "Poco/URI.h" // ------------------------------------------------------------------------------------------------ namespace SqMod { // ------------------------------------------------------------------------------------------------ using Poco::Net::HTTPClientSession; using Poco::Net::HTTPRequest; using Poco::Net::HTTPResponse; using Poco::Net::HTTPMessage; using Poco::StreamCopier; /* ------------------------------------------------------------------------------------------------ * Helper utility that can be used to announce the server to a master-list. */ struct SqAnnouncer : SqChainedInstances< SqAnnouncer > { /* -------------------------------------------------------------------------------------------- * Announcer status. */ bool mRun; /* -------------------------------------------------------------------------------------------- * Log failure messages. */ bool mLog; /* -------------------------------------------------------------------------------------------- * Announce count. */ uint32_t mCount; /* -------------------------------------------------------------------------------------------- * Success count (since last failure). */ uint32_t mSuccess; /* -------------------------------------------------------------------------------------------- * Failure count. */ uint32_t mFailures; /* -------------------------------------------------------------------------------------------- * Update interval (seconds). */ uint32_t mInterval; /* -------------------------------------------------------------------------------------------- * The number failures to ignore. */ uint32_t mRetries; /* -------------------------------------------------------------------------------------------- * Server port. */ const uint32_t mPort; /* -------------------------------------------------------------------------------------------- * Server version. */ const uint32_t mVersion; /* -------------------------------------------------------------------------------------------- * Master-list address. */ Poco::URI mURI; /* -------------------------------------------------------------------------------------------- * Synchronization mutex. */ std::mutex mMtx; /* -------------------------------------------------------------------------------------------- * Processing thread. */ std::thread mThread; /* -------------------------------------------------------------------------------------------- * Default interval constructor. */ explicit SqAnnouncer(StackStrF & uri) : SqAnnouncer(60,uri) { } /* -------------------------------------------------------------------------------------------- * Explicit constructor. */ SqAnnouncer(SQInteger interval, StackStrF & uri) : mRun(false), mLog(true) , mCount(0) , mSuccess(0) , mFailures(0) , mInterval(ClampL< SQInteger, uint32_t >(interval)) , mRetries(10) , mPort(GetServerPort()) , mVersion(GetServerVersion()) , mURI(uri.mPtr) , mMtx(), mThread() { // Remember this instance ChainInstance(); } /* -------------------------------------------------------------------------------------------- * Copy constructor (disabled). */ SqAnnouncer(const SqAnnouncer &) = delete; /* -------------------------------------------------------------------------------------------- * Move constructor (disabled). */ SqAnnouncer(SqAnnouncer &&) noexcept = delete; /* -------------------------------------------------------------------------------------------- * Destructor. */ ~SqAnnouncer() { // Forget about this instance UnchainInstance(); // Terminate the announcer if necessary Stop(); } /* -------------------------------------------------------------------------------------------- * Assignment operator (disabled). */ SqAnnouncer & operator = (const SqAnnouncer &) = delete; /* -------------------------------------------------------------------------------------------- * Move assignment (disabled). */ SqAnnouncer & operator = (SqAnnouncer &&) noexcept = delete; /* -------------------------------------------------------------------------------------------- * Start the announcer. */ SqAnnouncer & Run() { // Is the processing thread running? if (mThread.joinable()) { STHROWF("Announcer is already running"); } // Allow the loop to run mRun = true; // Create the processing thread mThread = std::thread(&SqAnnouncer::Proc, this); // Allow chaining return *this; } /* -------------------------------------------------------------------------------------------- * Start the announcer. */ SqAnnouncer & Stop() { // Is the processing thread running? if (mThread.joinable()) { // Acquire exclusive access mMtx.lock(); // Stop the loop mRun = false; // Yield exclusive access mMtx.unlock(); // Wait for the thread mThread.join(); } else mRun = false; // Just in case // Allow chaining return *this; } /* -------------------------------------------------------------------------------------------- * See if the announcer is running. */ bool Running() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mRun; } /* -------------------------------------------------------------------------------------------- * Check if logging is enabled when failed. */ bool GetLog() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mLog; } /* -------------------------------------------------------------------------------------------- * Set if logging is enabled when failed. */ void SetLog(bool toggle) { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Modify the requested value mLog = toggle; } /* -------------------------------------------------------------------------------------------- * Retrieve the announce interval (seconds). */ uint32_t GetInterval() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mInterval; } /* -------------------------------------------------------------------------------------------- * Modify the announce interval (seconds). */ void SetInterval(uint32_t seconds) { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Modify the requested value mInterval = seconds; } /* -------------------------------------------------------------------------------------------- * Retrieve the announce retries. */ uint32_t GetRetries() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mRetries; } /* -------------------------------------------------------------------------------------------- * Modify the announce retries. */ void SetRetries(uint32_t seconds) { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Modify the requested value mRetries = seconds; } /* -------------------------------------------------------------------------------------------- * Retrieve the announce count (total). */ uint32_t GetCount() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mCount; } /* -------------------------------------------------------------------------------------------- * Retrieve the announce success count (since last failure). */ uint32_t GetSuccess() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mSuccess; } /* -------------------------------------------------------------------------------------------- * Retrieve the announce failure count. */ uint32_t GetFailures() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mFailures; } /* -------------------------------------------------------------------------------------------- * Modify the announce URI. */ void SetURI(StackStrF & uri) { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Modify the requested value mURI = uri.mPtr; } /* -------------------------------------------------------------------------------------------- * Retrieve the announce URI. */ String GetURI() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Return the requested value return mURI.toString(); } protected: /* -------------------------------------------------------------------------------------------- * Processing thread. */ void Proc() { while (mRun) { try { using namespace std::chrono_literals; // Grab the current time point std::chrono::time_point< std::chrono::steady_clock > next = std::chrono::steady_clock::now(); // Set the time-point for next update next += std::chrono::seconds(mInterval); // Try to announce DoAnnounce(); // Sleep until the next appointed update time point for (std::chrono::time_point< std::chrono::steady_clock > curr; mRun && (next > curr); curr = std::chrono::steady_clock::now()) { std::this_thread::sleep_for(50ms); // Sleep for 1/20'th of a second } } catch (const Poco::Exception & e) { if (mLog) LogErr("Announcer failed: %s -> %s", e.what(), e.message().c_str()); } catch (const std::exception & e) { if (mLog) LogErr("Announcer failed: %s", e.what()); } catch (...) { LogFtl("Unknown exception while announcing. Announcer stopped"); // This is bad. Avoid a crash! return; } } } /* -------------------------------------------------------------------------------------------- * Announce succeeded. */ void DoSucceeded() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Increment announce counter mCount += 1; // Increment success counter mSuccess += 1; // Reset failure counter mFailures = 0; } /* -------------------------------------------------------------------------------------------- * Announce failed. */ void DoFailed() { // Acquire exclusive access to the announcer std::lock_guard< std::mutex > guard(mMtx); // Increment announce counter mCount += 1; // Reset success counter mSuccess = 0; // Increment failure counter mFailures += 1; // Check failure limit if (mFailures >= mRetries) { // Stop the announcer mRun = false; } } /* -------------------------------------------------------------------------------------------- * Processing thread. */ void DoAnnounce() { // Acquire exclusive access to the announcer mMtx.lock(); // Create a new HTTP client session HTTPClientSession session(mURI.getHost(), mURI.getPort()); // Create a new HTTP request HTTPRequest request(HTTPRequest::HTTP_POST, mURI.getPathAndQuery(), HTTPMessage::HTTP_1_1); // Set request content type header request.setContentType("application/x-www-form-urlencoded"); // Set the user agent for the announcer request.add("User-Agent", "VCMP/0.4"); // Set the server version for the announcer request.add("VCMP-Version", fmt::format("{}", mVersion)); // Generate the request body String body = fmt::format("port={}", mPort); // Release exclusive access to the announcer mMtx.unlock(); // No need to block while waiting for response // Create a new HTTP response HTTPResponse response; // Set the request body length request.setContentLength(body.size()); // Send the request try { std::ostream & os = session.sendRequest(request); // Send the content os << body; } catch (const Poco::Exception & e) { if (mLog) LogErr("Request failed: %s -> %s", e.what(), e.message().c_str()); // Failed! DoFailed(); // Probably the server is offline return; } catch (const std::exception & e) { if (mLog) LogErr("Request failed: %s", e.what()); // Failed! DoFailed(); // Probably the server is offline return; } // Receive the response std::istream & is = session.receiveResponse(response); // Validate the response from the server if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK) { std::stringstream ss; // Extract response contents StreamCopier::copyStream(is, ss); // Log the result if (mLog) LogErr("Announce failed: %s", ss.str().c_str()); // Failed! DoFailed(); } else { DoSucceeded(); // Succeeded! } } }; } // Namespace:: SqMod