diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini index 8b7d490..4d3472f 100644 --- a/bin/config_sample/config.ini +++ b/bin/config_sample/config.ini @@ -61,9 +61,6 @@ asset_url=http://attorneyoffline.de/base/ ; Whether or not the server should appear on the master server. advertise=true -; Wether the advertiser prints additional debug info -debug=false - ; The IP address of the master server. Unless something happens to the default one, you shouldn't change this. ms_ip=https://servers.aceattorneyonline.com/servers diff --git a/core.pro b/core.pro index 186577d..0e939c2 100644 --- a/core.pro +++ b/core.pro @@ -51,8 +51,8 @@ SOURCES += \ src/packets.cpp \ src/playerstateobserver.cpp \ src/server.cpp \ + src/serverpublisher.cpp \ src/testimony_recorder.cpp \ - src/advertiser.cpp \ src/logger/u_logger.cpp \ src/logger/writer_modcall.cpp \ src/logger/writer_full.cpp \ @@ -95,8 +95,8 @@ HEADERS += src/aoclient.h \ src/packet/packet_pr.h \ src/playerstateobserver.h \ src/server.h \ + src/serverpublisher.h \ src/typedefs.h \ - src/advertiser.h \ src/logger/u_logger.h \ src/logger/writer_modcall.h \ src/logger/writer_full.h \ diff --git a/src/advertiser.cpp b/src/advertiser.cpp deleted file mode 100644 index 0109e10..0000000 --- a/src/advertiser.cpp +++ /dev/null @@ -1,121 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////// -// akashi - a server for Attorney Online 2 // -// Copyright (C) 2020 scatterflower // -// // -// This program is free software: you can redistribute it and/or modify // -// it under the terms of the GNU Affero General Public License as // -// published by the Free Software Foundation, either version 3 of the // -// License, or (at your option) any later version. // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU Affero General Public License for more details. // -// // -// You should have received a copy of the GNU Affero General Public License // -// along with this program. If not, see . // -////////////////////////////////////////////////////////////////////////////////////// -#include "advertiser.h" - -#include "config_manager.h" - -Advertiser::Advertiser(int port) -{ - m_manager = new QNetworkAccessManager(); - connect(m_manager, &QNetworkAccessManager::finished, - this, &Advertiser::msRequestFinished); - - m_name = ConfigManager::serverName(); - m_hostname = ConfigManager::advertiserHostname(); - m_description = ConfigManager::serverDescription(); - - // Cheap workaround to correctly advertise when Cloudflare tunnel is used. - if (ConfigManager::advertiserCloudflareMode()) { - m_port = 80; - m_ws_port = 80; - } - else { - m_port = port; - m_ws_port = port; - } - - m_masterserver = ConfigManager::advertiserIP(); - m_debug = ConfigManager::advertiserDebug(); -} - -Advertiser::~Advertiser() -{ - m_manager->deleteLater(); -} - -void Advertiser::msAdvertiseServer() -{ - if (m_masterserver.isValid()) { - - QUrl url(m_masterserver); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QJsonObject l_json; - - if (!m_hostname.isEmpty()) { - l_json["ip"] = m_hostname; - } - - l_json["port"] = m_port; - if (m_ws_port != -1) { - l_json["ws_port"] = m_ws_port; - } - - l_json["players"] = m_players; - l_json["name"] = m_name; - - if (!m_description.isEmpty()) { - l_json["description"] = m_description; - } - - m_manager->post(request, QJsonDocument(l_json).toJson()); - - if (m_debug) - qDebug().noquote() << "Advertised Server"; - return; - } - if (m_debug) - qWarning().noquote() << "Unable to advertise. Masterserver URL '" + m_masterserver.toString() + "' is not valid."; - return; -} - -void Advertiser::msRequestFinished(QNetworkReply *f_reply) -{ - if (m_debug) { - if (f_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) { - qDebug().noquote() << "Succesfully advertised server."; - } - else { - QJsonDocument json = QJsonDocument::fromJson(f_reply->readAll()); - if (json.isNull()) { - qCritical().noquote() << "Invalid JSON response from" << f_reply->url(); - f_reply->deleteLater(); - return; - } - - qDebug().noquote() << "Got valid response from" << f_reply->url(); - qDebug() << json; - } - } - f_reply->deleteLater(); -} - -void Advertiser::updatePlayerCount(int f_current_players) -{ - m_players = f_current_players; -} - -void Advertiser::updateAdvertiserSettings() -{ - m_name = ConfigManager::serverName(); - m_hostname = ConfigManager::advertiserHostname(); - m_description = ConfigManager::serverDescription(); - m_masterserver = ConfigManager::advertiserIP(); - m_debug = ConfigManager::advertiserDebug(); -} diff --git a/src/advertiser.h b/src/advertiser.h deleted file mode 100644 index dc4c7e3..0000000 --- a/src/advertiser.h +++ /dev/null @@ -1,112 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////// -// akashi - a server for Attorney Online 2 // -// Copyright (C) 2020 scatterflower // -// // -// This program is free software: you can redistribute it and/or modify // -// it under the terms of the GNU Affero General Public License as // -// published by the Free Software Foundation, either version 3 of the // -// License, or (at your option) any later version. // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU Affero General Public License for more details. // -// // -// You should have received a copy of the GNU Affero General Public License // -// along with this program. If not, see . // -////////////////////////////////////////////////////////////////////////////////////// -#ifndef ADVERTISER_H -#define ADVERTISER_H - -#include -#include - -/** - * @brief Represents the advertiser of the server. Sends current server information to masterserver. - */ -class Advertiser : public QObject -{ - Q_OBJECT - - public: - /** - * @brief Constructor for the HTTP_Advertiser class. - */ - explicit Advertiser(int port); - - /** - * @brief Deconstructor for the HTTP_Advertiser class. Yes, that's it. Can't say more about it. - */ - ~Advertiser(); - - public slots: - - /** - * @brief Establishes a connection with masterserver to register or update the listing on the masterserver. - */ - void msAdvertiseServer(); - - /** - * @brief Reads the information send as a reply for further error handling. - * @param reply Response data from the masterserver. Information contained is send to the console if debug is enabled. - */ - void msRequestFinished(QNetworkReply *f_reply); - - /** - * @brief Updates the playercount of the server in the advertiser. - */ - void updatePlayerCount(int f_current_players); - - /** - * @brief Updates advertisement values - */ - void updateAdvertiserSettings(); - - private: - /** - * @brief Pointer to the network manager, necessary to execute POST requests to the masterserver. - */ - QNetworkAccessManager *m_manager; - - /** - * @brief Name of the server send to the masterserver. Changing this will change the display name in the serverlist - */ - QString m_name; - - /** - * @brief Optional hostname of the server. Can either be an IP or a DNS name. Disabled automatic IP detection of ms3. - */ - QString m_hostname; - - /** - * @brief Description of the server that is displayed in the client when the server is selected. - */ - QString m_description; - - /** - * @brief Client port for the AO2-Client. - */ - int m_port; - - /** - * @brief Websocket proxy port for WebAO users. - */ - int m_ws_port; - - /** - * @brief Maximum amount of clients that can be connected to the server. - */ - int m_players; - - /** - * @brief URL of the masterserver that m_manager posts to. This is almost never changed. - */ - QUrl m_masterserver; - - /** - * @brief Controls if network replies are printed to console. Should only be true if issues communicating with masterserver appear. - */ - bool m_debug; -}; - -#endif // ADVERTISER_H diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 87ad9a5..bf8c60a 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -626,28 +626,22 @@ QStringList ConfigManager::cdnList() return m_commands->cdns; } -bool ConfigManager::advertiseServer() +bool ConfigManager::publishServerEnabled() { return m_settings->value("Advertiser/advertise", "true").toBool(); } -bool ConfigManager::advertiserDebug() +QUrl ConfigManager::serverlistURL() { - return m_settings->value("Advertiser/debug", "true").toBool(); -} - -QUrl ConfigManager::advertiserIP() -{ - qDebug() << m_settings->value("Advertiser/ms_ip", "").toUrl(); return m_settings->value("Advertiser/ms_ip", "").toUrl(); } -QString ConfigManager::advertiserHostname() +QString ConfigManager::serverDomainName() { return m_settings->value("Advertiser/hostname", "").toString(); } -bool ConfigManager::advertiserCloudflareMode() +bool ConfigManager::advertiseWSProxy() { return m_settings->value("Advertiser/cloudflare_enabled", "false").toBool(); } diff --git a/src/config_manager.h b/src/config_manager.h index d8897db..2776354 100644 --- a/src/config_manager.h +++ b/src/config_manager.h @@ -431,29 +431,24 @@ class ConfigManager /** * @brief Returns if the advertiser is enabled to advertise on ms3. */ - static bool advertiseServer(); - - /** - * @brief Returns if the advertiser prints debug info to console. - */ - static bool advertiserDebug(); + static bool publishServerEnabled(); /** * @brief Returns the IP or URL of the masterserver. */ - static QUrl advertiserIP(); + static QUrl serverlistURL(); /** * @brief Returns an optional hostname paramemter for the advertiser. * If used allows user to set a custom IP or domain name. */ - static QString advertiserHostname(); + static QString serverDomainName(); /** * @brief Returns a dummy port instead of the real port * @return */ - static bool advertiserCloudflareMode(); + static bool advertiseWSProxy(); /** * @brief Returns the uptime of the server in miliseconds. diff --git a/src/main.cpp b/src/main.cpp index 06f24cc..e38d7eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,7 +45,7 @@ int main(int argc, char *argv[]) QCoreApplication::quit(); } else { - server = new Server(ConfigManager::serverPort()); + server = new Server(ConfigManager::serverPort(), &app); server->start(); } diff --git a/src/server.cpp b/src/server.cpp index 554edc3..80db537 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -18,7 +18,6 @@ #include "server.h" #include "acl_roles_handler.h" -#include "advertiser.h" #include "aoclient.h" #include "area_data.h" #include "command_extension.h" @@ -29,6 +28,7 @@ #include "music_manager.h" #include "network/network_socket.h" #include "packet/packet_factory.h" +#include "serverpublisher.h" Server::Server(int p_ws_port, QObject *parent) : QObject(parent), @@ -81,17 +81,7 @@ void Server::start() handleDiscordIntegration(); // Construct modern advertiser if enabled in config - if (ConfigManager::advertiseServer()) { - AdvertiserTimer = new QTimer(this); - ms3_Advertiser = new Advertiser(server->serverPort()); - - connect(AdvertiserTimer, &QTimer::timeout, ms3_Advertiser, &Advertiser::msAdvertiseServer); - connect(this, &Server::playerCountUpdated, ms3_Advertiser, &Advertiser::updatePlayerCount); - connect(this, &Server::updateHTTPConfiguration, ms3_Advertiser, &Advertiser::updateAdvertiserSettings); - emit playerCountUpdated(m_player_count); - ms3_Advertiser->msAdvertiseServer(); - AdvertiserTimer->start(300000); - } + server_publisher = new ServerPublisher(server->serverPort(), &m_player_count, this); // Get characters from config file m_characters = ConfigManager::charlist(); diff --git a/src/server.h b/src/server.h index 443253c..a0febe4 100644 --- a/src/server.h +++ b/src/server.h @@ -33,7 +33,7 @@ #include "playerstateobserver.h" class ACLRolesHandler; -class Advertiser; +class ServerPublisher; class AOClient; class AreaData; class CommandExtensionCollection; @@ -421,12 +421,7 @@ class Server : public QObject /** * @brief Handles HTTP server advertising. */ - Advertiser *ms3_Advertiser; - - /** - * @brief Advertises the server in a regular intervall. - */ - QTimer *AdvertiserTimer; + ServerPublisher *server_publisher; /** * @brief Handles the universal log framework. diff --git a/src/serverpublisher.cpp b/src/serverpublisher.cpp new file mode 100644 index 0000000..4367248 --- /dev/null +++ b/src/serverpublisher.cpp @@ -0,0 +1,107 @@ +////////////////////////////////////////////////////////////////////////////////////// +// akashi - a server for Attorney Online 2 // +// Copyright (C) 2020 scatterflower // +// // +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the GNU Affero General Public License as // +// published by the Free Software Foundation, either version 3 of the // +// License, or (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU Affero General Public License for more details. // +// // +// You should have received a copy of the GNU Affero General Public License // +// along with this program. If not, see . // +////////////////////////////////////////////////////////////////////////////////////// +#include "serverpublisher.h" +#include "config_manager.h" +#include "qnamespace.h" + +#include +#include +#include + +const int HTTP_OK = 200; +const int WS_REVERSE_PROXY = 80; +const int TIMEOUT = 1000 * 60 * 5; + +ServerPublisher::ServerPublisher(int port, int *player_count, QObject *parent) : + QObject(parent), + m_manager{new QNetworkAccessManager(this)}, + timeout_timer(new QTimer(this)), + m_players(player_count), + m_port{port} +{ + connect(m_manager, &QNetworkAccessManager::finished, this, &ServerPublisher::finished); + connect(timeout_timer, &QTimer::timeout, this, &ServerPublisher::publishServer); + + timeout_timer->setTimerType(Qt::PreciseTimer); + timeout_timer->setInterval(TIMEOUT); + timeout_timer->start(); + publishServer(); +} + +void ServerPublisher::publishServer() +{ + if (!ConfigManager::publishServerEnabled()) { + return; + } + + QUrl serverlist(ConfigManager::serverlistURL()); + if (serverlist.isValid()) { + QNetworkRequest request(serverlist); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QJsonObject serverinfo; + if (!ConfigManager::serverDomainName().trimmed().isEmpty()) { + serverinfo["ip"] = ConfigManager::serverDomainName(); + } + serverinfo["port"] = 27106; + serverinfo["ws_port"] = ConfigManager::advertiseWSProxy() ? WS_REVERSE_PROXY : m_port; + serverinfo["players"] = *m_players; + serverinfo["name"] = ConfigManager::serverName(); + serverinfo["description"] = ConfigManager::serverDescription(); + + m_manager->post(request, QJsonDocument(serverinfo).toJson()); + } + else { + qWarning() << "Failed to advertise server. Serverlist URL is not valid. URL:" << serverlist.toString(); + } +} + +void ServerPublisher::finished(QNetworkReply *f_reply) +{ + QNetworkReply *reply(f_reply); + QString remote_url = reply->url().toString(); + + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "Unable to connect to serverlist due to the following error:" << reply->errorString(); + qWarning() << "Remote URL:" << remote_url; + } + + QByteArray data = reply->isReadable() ? reply->readAll() : QByteArray(); + const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != HTTP_OK) { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError || !document.isObject()) { + qWarning() << "Received malformed response from masterserver. Error:" << error.errorString(); + return; + } + + QJsonObject body = document.object(); + if (body.contains("errors")) { + qWarning() << "Failed to advertise to the serverlist due to the following errors:"; + const QJsonArray errors = body["errors"].toArray(); + for (const auto &ref : errors) { + QJsonObject error = ref.toObject(); + qWarning().noquote() << "Error:" << error["type"].toString() << ". Message:" << error["message"].toString(); + } + return; + } + } + qInfo() << "Sucessfully advertised server to serverlist."; +} diff --git a/src/serverpublisher.h b/src/serverpublisher.h new file mode 100644 index 0000000..b0a1b16 --- /dev/null +++ b/src/serverpublisher.h @@ -0,0 +1,69 @@ +////////////////////////////////////////////////////////////////////////////////////// +// akashi - a server for Attorney Online 2 // +// Copyright (C) 2020 scatterflower // +// // +// This program is free software: you can redistribute it and/or modify // +// it under the terms of the GNU Affero General Public License as // +// published by the Free Software Foundation, either version 3 of the // +// License, or (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU Affero General Public License for more details. // +// // +// You should have received a copy of the GNU Affero General Public License // +// along with this program. If not, see . // +////////////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include + +class QNetworkAccessManager; +class QNetworkReply; +class QTimer; + +/** + * @brief Represents the ServerPublisher of the server. Sends current server information to the serverlist. + */ +class ServerPublisher : public QObject +{ + Q_OBJECT + + public: + explicit ServerPublisher(int port, int *player_count, QObject *parent = nullptr); + virtual ~ServerPublisher(){}; + + public slots: + + /** + * @brief Establishes a connection with masterserver to register or update the listing on the masterserver. + */ + void publishServer(); + + /** + * @brief Reads the response from the serverlist. + */ + void finished(QNetworkReply *f_reply); + + private: + /** + * @brief Pointer to the network manager, necessary to execute POST requests to the masterserver. + */ + QNetworkAccessManager *m_manager; + + /** + * @brief Advertisers when it expires. + */ + QTimer *timeout_timer; + + /** + * @brief The current amount of players on the server. + */ + int *m_players; + + /** + * @brief The WS port of the server. + */ + int m_port; +}; diff --git a/tests/unittest_config_manager/tst_unittest_config_manager.cpp b/tests/unittest_config_manager/tst_unittest_config_manager.cpp index 7737c3b..b0eeee8 100644 --- a/tests/unittest_config_manager/tst_unittest_config_manager.cpp +++ b/tests/unittest_config_manager/tst_unittest_config_manager.cpp @@ -130,15 +130,15 @@ class tst_ConfigManager : public QObject void cdnList(); - void advertiseServer(); + void publishServerEnabled(); void advertiserDebug(); - void advertiserIP(); + void serverlistURL(); - void advertiserHostname(); + void serverDomainName(); - void advertiserCloudflareMode(); + void advertiseWSProxy(); }; void tst_ConfigManager::verifyServerConfig() @@ -426,7 +426,7 @@ void tst_ConfigManager::cdnList() { } -void tst_ConfigManager::advertiseServer() +void tst_ConfigManager::publishServerEnabled() { } @@ -434,15 +434,15 @@ void tst_ConfigManager::advertiserDebug() { } -void tst_ConfigManager::advertiserIP() +void tst_ConfigManager::serverlistURL() { } -void tst_ConfigManager::advertiserHostname() +void tst_ConfigManager::serverDomainName() { } -void tst_ConfigManager::advertiserCloudflareMode() +void tst_ConfigManager::advertiseWSProxy() { }