Implement a basic websockets server

This commit is contained in:
KimLS
2019-05-16 00:12:21 -07:00
parent 5bfcef600f
commit ebca112769
16 changed files with 734 additions and 307 deletions
+3 -5
View File
@@ -5,8 +5,9 @@ SET(zone_sources
aa_ability.cpp
aggro.cpp
aggromanager.cpp
aura.cpp
api_service.cpp
attack.cpp
aura.cpp
beacon.cpp
bonuses.cpp
bot.cpp
@@ -17,7 +18,6 @@ SET(zone_sources
client_mods.cpp
client_packet.cpp
client_process.cpp
console.cpp
command.cpp
corpse.cpp
data_bucket.cpp
@@ -29,7 +29,6 @@ SET(zone_sources
embxs.cpp
encounter.cpp
entity.cpp
eqemu_api_zone_data_service.cpp
exp.cpp
fastmath.cpp
fearpath.cpp
@@ -149,6 +148,7 @@ SET(zone_headers
aa.h
aa_ability.h
aggromanager.h
api_service.h
aura.h
basic_functions.h
beacon.h
@@ -160,7 +160,6 @@ SET(zone_headers
client_packet.h
command.h
common.h
console.h
corpse.h
data_bucket.h
doors.h
@@ -169,7 +168,6 @@ SET(zone_headers
embxs.h
encounter.h
entity.h
eqemu_api_zone_data_service.h
errmsg.h
event_codes.h
fastmath.h
@@ -18,10 +18,14 @@
*
*/
#include <memory>
#include "../common/net/websocket_server.h"
#include "../common/eqemu_logsys.h"
#include "zonedb.h"
#include "client.h"
#include "entity.h"
#include "corpse.h"
#include "eqemu_api_zone_data_service.h"
#include "api_service.h"
#include "npc.h"
#include "object.h"
#include "zone.h"
@@ -30,8 +34,117 @@
extern Zone *zone;
void callGetNpcListDetail(Json::Value &response)
{
EQ::Net::WebsocketLoginStatus CheckLogin(EQ::Net::WebsocketServerConnection *connection, const std::string &username, const std::string &password) {
EQ::Net::WebsocketLoginStatus ret;
ret.logged_in = false;
ret.account_id = database.CheckLogin(username.c_str(), password.c_str());
if (ret.account_id == 0) {
return ret;
}
char account_name[64];
database.GetAccountName(static_cast<uint32>(ret.account_id), account_name);
ret.account_name = account_name;
ret.logged_in = true;
ret.status = database.CheckStatus(ret.account_id);
return ret;
}
Json::Value ApiGetPacketStatistics(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
auto &list = entity_list.GetClientList();
for (auto &iter : list) {
auto client = iter.second;
auto connection = client->Connection();
auto opts = connection->GetManager()->GetOptions();
auto eqs_stats = connection->GetStats();
auto &stats = eqs_stats.DaybreakStats;
auto now = EQ::Net::Clock::now();
auto sec_since_stats_reset = std::chrono::duration_cast<std::chrono::duration<double>>(
now - stats.created
).count();
Json::Value row;
row["client_id"] = client->GetID();
row["client_name"] = client->GetCleanName();
row["seconds_since_reset"] = sec_since_stats_reset;
row["sent_bytes"] = stats.sent_bytes;
row["receive_bytes"] = stats.recv_bytes;
row["min_ping"] = stats.min_ping;
row["max_ping"] = stats.max_ping;
row["last_ping"] = stats.last_ping;
row["average_ping"] = stats.avg_ping;
row["realtime_receive_packets"] = stats.recv_packets;
row["realtime_sent_packets"] = stats.sent_packets;
row["sync_recv_packets"] = stats.sync_recv_packets;
row["sync_sent_packets"] = stats.sync_sent_packets;
row["sync_remote_recv_packets"] = stats.sync_remote_recv_packets;
row["sync_remote_sent_packets"] = stats.sync_remote_sent_packets;
row["packet_loss_in"] = (100.0 * (1.0 - static_cast<double>(stats.sync_recv_packets) /
static_cast<double>(stats.sync_remote_sent_packets)));
row["packet_loss_out"] = (100.0 * (1.0 - static_cast<double>(stats.sync_remote_recv_packets) /
static_cast<double>(stats.sync_sent_packets)));
row["resent_packets"] = stats.resent_packets;
row["resent_fragments"] = stats.resent_fragments;
row["resent_non_fragments"] = stats.resent_full;
row["dropped_datarate_packets"] = stats.dropped_datarate_packets;
Json::Value sent_packet_types;
for (auto i = 0; i < _maxEmuOpcode; ++i) {
auto count = eqs_stats.SentCount[i];
if (count > 0) {
sent_packet_types[OpcodeNames[i]] = count;
}
}
Json::Value receive_packet_types;
for (auto i = 0; i < _maxEmuOpcode; ++i) {
auto count = eqs_stats.RecvCount[i];
if (count > 0) {
receive_packet_types[OpcodeNames[i]] = count;
}
}
row["sent_packet_types"] = sent_packet_types;
row["receive_packet_types"] = receive_packet_types;
response.append(row);
}
return response;
}
Json::Value ApiGetOpcodeList(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
for (auto i = 0; i < _maxEmuOpcode; ++i) {
Json::Value row = OpcodeNames[i];
response.append(row);
}
return response;
}
Json::Value ApiGetNpcListDetail(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
auto &list = entity_list.GetNPCList();
for (auto &iter : list) {
@@ -115,10 +228,16 @@ void callGetNpcListDetail(Json::Value &response)
response.append(row);
}
return response;
}
void callGetDoorListDetail(Json::Value &response)
{
Json::Value ApiGetDoorListDetail(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
auto &door_list = entity_list.GetDoorsList();
for (auto itr : door_list) {
@@ -152,10 +271,16 @@ void callGetDoorListDetail(Json::Value &response)
response.append(row);
}
return response;
}
void callGetCorpseListDetail(Json::Value &response)
{
Json::Value ApiGetCorpseListDetail(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
auto &corpse_list = entity_list.GetCorpseList();
for (auto itr : corpse_list) {
@@ -185,10 +310,16 @@ void callGetCorpseListDetail(Json::Value &response)
response.append(row);
}
return response;
}
void callGetObjectListDetail(Json::Value &response)
{
Json::Value ApiGetObjectListDetail(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
auto &list = entity_list.GetObjectList();
for (auto &iter : list) {
@@ -214,10 +345,16 @@ void callGetObjectListDetail(Json::Value &response)
response.append(row);
}
return response;
}
void callGetMobListDetail(Json::Value &response)
{
Json::Value ApiGetMobListDetail(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
auto &list = entity_list.GetMobList();
for (auto &iter : list) {
@@ -414,10 +551,16 @@ void callGetMobListDetail(Json::Value &response)
response.append(row);
}
return response;
}
void callGetClientListDetail(Json::Value &response)
{
Json::Value ApiGetClientListDetail(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
auto &list = entity_list.GetClientList();
for (auto &iter : list) {
@@ -607,10 +750,16 @@ void callGetClientListDetail(Json::Value &response)
response.append(row);
}
return response;
}
void callGetZoneAttributes(Json::Value &response)
{
Json::Value ApiGetZoneAttributes(EQ::Net::WebsocketServerConnection *connection, Json::Value params) {
if (zone->GetZoneID() == 0) {
throw EQ::Net::WebsocketException("Zone must be loaded to invoke this call");
}
Json::Value response;
Json::Value row;
row["aggro_limit_reached"] = zone->AggroLimitReached();
@@ -648,129 +797,31 @@ void callGetZoneAttributes(Json::Value &response)
row["zone_type"] = zone->GetZoneType();
response.append(row);
return response;
}
void callGetPacketStatistics(Json::Value &response)
void RegisterApiLogEvent(std::unique_ptr<EQ::Net::WebsocketServer> &server)
{
auto &list = entity_list.GetClientList();
for (auto &iter : list) {
auto client = iter.second;
auto connection = client->Connection();
auto opts = connection->GetManager()->GetOptions();
auto eqs_stats = connection->GetStats();
auto &stats = eqs_stats.DaybreakStats;
auto now = EQ::Net::Clock::now();
auto sec_since_stats_reset = std::chrono::duration_cast<std::chrono::duration<double>>(
now - stats.created
).count();
Json::Value row;
row["client_id"] = client->GetID();
row["client_name"] = client->GetCleanName();
row["seconds_since_reset"] = sec_since_stats_reset;
row["sent_bytes"] = stats.sent_bytes;
row["receive_bytes"] = stats.recv_bytes;
row["min_ping"] = stats.min_ping;
row["max_ping"] = stats.max_ping;
row["last_ping"] = stats.last_ping;
row["average_ping"] = stats.avg_ping;
row["realtime_receive_packets"] = stats.recv_packets;
row["realtime_sent_packets"] = stats.sent_packets;
row["sync_recv_packets"] = stats.sync_recv_packets;
row["sync_sent_packets"] = stats.sync_sent_packets;
row["sync_remote_recv_packets"] = stats.sync_remote_recv_packets;
row["sync_remote_sent_packets"] = stats.sync_remote_sent_packets;
row["packet_loss_in"] = (100.0 * (1.0 - static_cast<double>(stats.sync_recv_packets) /
static_cast<double>(stats.sync_remote_sent_packets)));
row["packet_loss_out"] = (100.0 * (1.0 - static_cast<double>(stats.sync_remote_recv_packets) /
static_cast<double>(stats.sync_sent_packets)));
row["resent_packets"] = stats.resent_packets;
row["resent_fragments"] = stats.resent_fragments;
row["resent_non_fragments"] = stats.resent_full;
row["dropped_datarate_packets"] = stats.dropped_datarate_packets;
Json::Value sent_packet_types;
for (auto i = 0; i < _maxEmuOpcode; ++i) {
auto count = eqs_stats.SentCount[i];
if (count > 0) {
sent_packet_types[OpcodeNames[i]] = count;
}
}
Json::Value receive_packet_types;
for (auto i = 0; i < _maxEmuOpcode; ++i) {
auto count = eqs_stats.RecvCount[i];
if (count > 0) {
receive_packet_types[OpcodeNames[i]] = count;
}
}
row["sent_packet_types"] = sent_packet_types;
row["receive_packet_types"] = receive_packet_types;
response.append(row);
}
LogSys.SetConsoleHandler([&](uint16 debug_level, uint16 log_category, const std::string &msg) {
Json::Value data;
data["debug_level"] = debug_level;
data["log_category"] = log_category;
data["msg"] = msg;
server->DispatchEvent("log", data, 50);
});
}
void RegisterApiService(std::unique_ptr<EQ::Net::WebsocketServer> &server) {
server->SetLoginHandler(CheckLogin);
server->SetMethodHandler("get_packet_statistics", &ApiGetPacketStatistics, 50);
server->SetMethodHandler("get_opcode_list", &ApiGetOpcodeList, 50);
server->SetMethodHandler("get_npc_list_detail", &ApiGetNpcListDetail, 50);
server->SetMethodHandler("get_door_list_detail", &ApiGetDoorListDetail, 50);
server->SetMethodHandler("get_corpse_list_detail", &ApiGetCorpseListDetail, 50);
server->SetMethodHandler("get_object_list_detail", &ApiGetObjectListDetail, 50);
server->SetMethodHandler("get_mob_list_detail", &ApiGetMobListDetail, 50);
server->SetMethodHandler("get_client_list_detail", &ApiGetClientListDetail, 50);
server->SetMethodHandler("get_zone_attributes", &ApiGetZoneAttributes, 50);
void callGetOpcodeList(Json::Value &response)
{
for (auto i = 0; i < _maxEmuOpcode; ++i) {
Json::Value row = OpcodeNames[i];
response.append(row);
}
}
void EQEmuApiZoneDataService::get(Json::Value &response, const std::vector<std::string> &args)
{
std::string method = args[0];
if (zone->GetZoneID() == 0) {
response["error"] = "Zone must be loaded to invoke calls";
return;
}
/**
* Packet statistics
*/
if (method == "get_packet_statistics") {
callGetPacketStatistics(response);
}
if (method == "get_opcode_list") {
callGetOpcodeList(response);
}
/**
* List detail
*/
if (method == "get_npc_list_detail") {
callGetNpcListDetail(response);
}
if (method == "get_client_list_detail") {
callGetClientListDetail(response);
}
if (method == "get_mob_list_detail") {
callGetMobListDetail(response);
}
if (method == "get_door_list_detail") {
callGetDoorListDetail(response);
}
if (method == "get_corpse_list_detail") {
callGetCorpseListDetail(response);
}
if (method == "get_object_list_detail") {
callGetObjectListDetail(response);
}
/**
* Zone attributes
*/
if (method == "get_zone_attributes") {
callGetZoneAttributes(response);
}
RegisterApiLogEvent(server);
}
+4 -3
View File
@@ -1,6 +1,6 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2018 EQEmulator Development Team (https://github.com/EQEmu/Server)
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
#pragma once
#include "../common/net/console_server.h"
#include <memory>
#include "../common/net/websocket_server.h"
void RegisterConsoleFunctions(std::unique_ptr<EQ::Net::ConsoleServer> &console);
void RegisterApiService(std::unique_ptr<EQ::Net::WebsocketServer> &server);
-122
View File
@@ -1,122 +0,0 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "console.h"
#include "../common/string_util.h"
#include "../common/md5.h"
#include "../common/database.h"
#include "../common/json/json.h"
#include "zone.h"
#include "npc.h"
#include "entity.h"
#include "eqemu_api_zone_data_service.h"
/**
* @param username
* @param password
* @return
*/
struct EQ::Net::ConsoleLoginStatus CheckLogin(const std::string &username, const std::string &password)
{
struct EQ::Net::ConsoleLoginStatus ret;
ret.account_id = database.CheckLogin(username.c_str(), password.c_str());
if (ret.account_id == 0) {
return ret;
}
char account_name[64];
database.GetAccountName(static_cast<uint32>(ret.account_id), account_name);
ret.account_name = account_name;
ret.status = database.CheckStatus(static_cast<uint32>(ret.account_id));
return ret;
}
/**
* @param connection
* @param command
* @param args
*/
void ConsoleApi(
EQ::Net::ConsoleServerConnection *connection,
const std::string &command,
const std::vector<std::string> &args
)
{
Json::Value root;
Json::Value response;
BenchTimer timer;
timer.reset();
EQEmuApiZoneDataService::get(response, args);
std::string method = args[0];
root["execution_time"] = std::to_string(timer.elapsed());
root["method"] = method;
root["data"] = response;
std::stringstream payload;
payload << root;
connection->SendLine(payload.str());
}
/**
*
* @param connection
* @param command
* @param args
*/
void ConsoleNull(
EQ::Net::ConsoleServerConnection *connection,
const std::string &command,
const std::vector<std::string> &args
)
{
}
/**
* @param connection
* @param command
* @param args
*/
void ConsoleQuit(
EQ::Net::ConsoleServerConnection *connection,
const std::string &command,
const std::vector<std::string> &args
)
{
connection->SendLine("Exiting...");
connection->Close();
}
/**
* @param console
*/
void RegisterConsoleFunctions(std::unique_ptr<EQ::Net::ConsoleServer> &console)
{
console->RegisterLogin(std::bind(CheckLogin, std::placeholders::_1, std::placeholders::_2));
console->RegisterCall("api", 200, "api", std::bind(ConsoleApi, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("ping", 50, "ping", std::bind(ConsoleNull, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("quit", 50, "quit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
console->RegisterCall("exit", 50, "exit", std::bind(ConsoleQuit, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
-32
View File
@@ -1,32 +0,0 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EQEMU_API_ZONE_DATA_SERVICE_H
#define EQEMU_API_ZONE_DATA_SERVICE_H
#include "../common/json/json.h"
class EQEmuApiZoneDataService {
public:
static void get(Json::Value &response, const std::vector<std::string> &args);
};
#endif //EQEMU_API_ZONE_DATA_SERVICE_H
+5 -9
View File
@@ -43,8 +43,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/spdat.h"
#include "../common/eqemu_logsys.h"
#include "../common/eqemu_logsys_fmt.h"
#include "../common/net/console_server.h"
#include "api_service.h"
#include "zone_config.h"
#include "masterentity.h"
#include "worldserver.h"
@@ -93,9 +93,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#else
#include <pthread.h>
#include "../common/unix.h"
#include "../common/net/console_server.h"
#include "console.h"
#endif
volatile bool RunLoops = true;
@@ -121,7 +118,6 @@ double frame_time = 0.0;
void Shutdown();
extern void MapOpcodes();
void RegisterConsoleFunctions(std::unique_ptr<EQ::Net::ConsoleServer> &console);
int main(int argc, char** argv) {
RegisterExecutablePlatform(ExePlatformZone);
@@ -255,7 +251,7 @@ int main(int argc, char** argv) {
#endif
/* Register Log System and Settings */
LogSys.OnLogHookCallBackZone(&Zone::GMSayHookCallBackProcess);
LogSys.SetGMSayHandler(&Zone::GMSayHookCallBackProcess);
database.LoadLogSettings(LogSys.log_settings);
LogSys.StartFileLogs();
@@ -463,7 +459,7 @@ int main(int argc, char** argv) {
EQStreamInterface *eqsi;
std::unique_ptr<EQ::Net::EQStreamManager> eqsm;
std::chrono::time_point<std::chrono::system_clock> frame_prev = std::chrono::system_clock::now();
std::unique_ptr<EQ::Net::ConsoleServer> console;
std::unique_ptr<EQ::Net::WebsocketServer> ws_server;
auto loop_fn = [&](EQ::Timer* t) {
//Advance the timer to our current point in time
@@ -488,8 +484,8 @@ int main(int argc, char** argv) {
Config->TelnetIP.c_str(),
Config->ZonePort
);
console.reset(new EQ::Net::ConsoleServer(Config->TelnetIP, Config->ZonePort));
RegisterConsoleFunctions(console);
ws_server.reset(new EQ::Net::WebsocketServer(Config->TelnetIP, Config->ZonePort));
RegisterApiService(ws_server);
telnet_server_opened = true;
}
}