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
.gitmodules vendored
View File

@ -13,3 +13,6 @@
[submodule "submodules/recastnavigation"]
path = submodules/recastnavigation
url = https://github.com/recastnavigation/recastnavigation.git
[submodule "submodules/websocketpp"]
path = submodules/websocketpp
url = https://github.com/zaphoyd/websocketpp.git

View File

@ -349,6 +349,7 @@ INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/submodules/recastnavigat
INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/submodules/recastnavigation/DetourCrowd/Include")
INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/submodules/recastnavigation/DetourTileCache/Include")
INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/submodules/recastnavigation/Recast/Include")
INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/submodules/websocketpp")
IF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS OR EQEMU_BUILD_HC)
ADD_SUBDIRECTORY(common)

View File

@ -86,6 +86,8 @@ SET(common_sources
net/servertalk_server_connection.cpp
net/tcp_connection.cpp
net/tcp_server.cpp
net/websocket_server.cpp
net/websocket_server_connection.cpp
patches/patches.cpp
patches/sod.cpp
patches/sod_limits.cpp
@ -230,6 +232,8 @@ SET(common_headers
net/servertalk_server_connection.h
net/tcp_connection.h
net/tcp_server.h
net/websocket_server.h
net/websocket_server_connection.h
patches/patches.h
patches/sod.h
patches/sod_limits.h
@ -309,6 +313,10 @@ SOURCE_GROUP(Net FILES
net/tcp_connection.h
net/tcp_server.cpp
net/tcp_server.h
net/websocket_server.cpp
net/websocket_server.h
net/websocket_server_connection.cpp
net/websocket_server_connection.h
)
SOURCE_GROUP(Patches FILES

View File

@ -95,6 +95,7 @@ enum GameChatColor {
EQEmuLogSys::EQEmuLogSys()
{
on_log_gmsay_hook = [](uint16 log_type, const std::string &) {};
on_log_console_hook = [](uint16 debug_level, uint16 log_type, const std::string &) {};
bool file_logs_enabled = false;
int log_platform = 0;
}
@ -346,6 +347,8 @@ void EQEmuLogSys::ProcessConsoleMessage(uint16 debug_level, uint16 log_category,
#else
std::cout << EQEmuLogSys::GetLinuxConsoleColorFromCategory(log_category) << message << LC_RESET << std::endl;
#endif
on_log_console_hook(debug_level, log_category, message);
}
/**
@ -505,4 +508,4 @@ void EQEmuLogSys::StartFileLogs(const std::string &log_name)
std::ios_base::app | std::ios_base::out
);
}
}
}

View File

@ -240,7 +240,8 @@ public:
*/
uint16 GetGMSayColorFromCategory(uint16 log_category);
void OnLogHookCallBackZone(std::function<void(uint16 log_type, const std::string&)> f) { on_log_gmsay_hook = f; }
void SetGMSayHandler(std::function<void(uint16 log_type, const std::string&)> f) { on_log_gmsay_hook = f; }
void SetConsoleHandler(std::function<void(uint16 debug_level, uint16 log_type, const std::string&)> f) { on_log_console_hook = f; }
private:
@ -248,6 +249,7 @@ private:
* Callback pointer to zone process for hooking logs to zone using GMSay
*/
std::function<void(uint16 log_category, const std::string&)> on_log_gmsay_hook;
std::function<void(uint16 debug_level, uint16 log_category, const std::string&)> on_log_console_hook;
/**
* Formats log messages like '[Category] This is a log message'

View File

@ -0,0 +1,216 @@
#include "websocket_server.h"
#include "../event/event_loop.h"
#include "../event/timer.h"
#include <fmt/format.h>
#include <map>
#include <list>
struct MethodHandlerEntry
{
MethodHandlerEntry(EQ::Net::WebsocketServer::MethodHandler h, int s) {
handler = h;
status = s;
}
EQ::Net::WebsocketServer::MethodHandler handler;
int status;
};
struct EQ::Net::WebsocketServer::Impl
{
std::unique_ptr<TCPServer> server;
std::unique_ptr<EQ::Timer> ping_timer;
std::map<std::shared_ptr<websocket_connection>, std::unique_ptr<WebsocketServerConnection>> connections;
std::map<std::string, MethodHandlerEntry> methods;
websocket_server websocket_server;
LoginHandler login_handler;
};
EQ::Net::WebsocketServer::WebsocketServer(const std::string &addr, int port)
{
_impl.reset(new Impl());
_impl->server.reset(new EQ::Net::TCPServer());
_impl->server->Listen(addr, port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
auto wsc = _impl->websocket_server.get_connection();
WebsocketServerConnection *c = new WebsocketServerConnection(this, connection, wsc);
_impl->connections.insert(std::make_pair(wsc, std::unique_ptr<WebsocketServerConnection>(c)));
});
_impl->websocket_server.set_write_handler(
[this](websocketpp::connection_hdl hdl, char const *data, size_t size) -> websocketpp::lib::error_code {
auto c = _impl->websocket_server.get_con_from_hdl(hdl);
auto iter = _impl->connections.find(c);
if (iter != _impl->connections.end()) {
iter->second->GetTCPConnection()->Write(data, size);
}
return websocketpp::lib::error_code();
});
_impl->ping_timer.reset(new EQ::Timer(5000, true, [this](EQ::Timer *t) {
auto iter = _impl->connections.begin();
while (iter != _impl->connections.end()) {
try {
auto &connection = iter->second;
connection->GetWebsocketConnection()->ping("keepalive");
}
catch (std::exception) {
iter->second->GetTCPConnection()->Disconnect();
}
iter++;
}
}));
_impl->methods.insert(std::make_pair("login", MethodHandlerEntry(std::bind(&WebsocketServer::Login, this, std::placeholders::_1, std::placeholders::_2), 0)));
_impl->methods.insert(std::make_pair("subscribe", MethodHandlerEntry(std::bind(&WebsocketServer::Subscribe, this, std::placeholders::_1, std::placeholders::_2), 0)));
_impl->methods.insert(std::make_pair("unsubscribe", MethodHandlerEntry(std::bind(&WebsocketServer::Unsubscribe, this, std::placeholders::_1, std::placeholders::_2), 0)));
_impl->login_handler = [](const WebsocketServerConnection* connection, const std::string& user, const std::string& pass) {
WebsocketLoginStatus ret;
ret.account_name = "admin";
if (connection->RemoteIP() == "127.0.0.1" || connection->RemoteIP() == "::") {
ret.logged_in = true;
return ret;
}
ret.logged_in = false;
return ret;
};
_impl->websocket_server.clear_access_channels(websocketpp::log::alevel::all);
}
EQ::Net::WebsocketServer::~WebsocketServer()
{
}
void EQ::Net::WebsocketServer::ReleaseConnection(WebsocketServerConnection *connection)
{
//Clear any subscriptions here
_impl->connections.erase(connection->GetWebsocketConnection());
}
Json::Value EQ::Net::WebsocketServer::HandleRequest(WebsocketServerConnection *connection, const std::string &method, const Json::Value &params)
{
Json::Value err;
if (method != "login") {
if (!connection->IsAuthorized()) {
throw WebsocketException("Not logged in");
}
}
auto iter = _impl->methods.find(method);
if (iter != _impl->methods.end()) {
auto &s = iter->second;
if (s.status > connection->GetStatus()) {
throw WebsocketException("Status too low");
}
return s.handler(connection, params);
}
throw WebsocketException("Unknown Method");
}
void EQ::Net::WebsocketServer::SetMethodHandler(const std::string &method, MethodHandler handler, int required_status)
{
//Reserved method names
if (method == "subscribe" ||
method == "unsubscribe" ||
method == "login") {
return;
}
_impl->methods.insert_or_assign(method, MethodHandlerEntry(handler, required_status));
}
void EQ::Net::WebsocketServer::SetLoginHandler(LoginHandler handler)
{
_impl->login_handler = handler;
}
void EQ::Net::WebsocketServer::DispatchEvent(const std::string &evt, Json::Value data, int required_status)
{
try {
Json::Value event_obj;
event_obj["type"] = "event";
event_obj["event"] = evt;
event_obj["data"] = data;
std::stringstream payload;
payload << event_obj;
for (auto &iter : _impl->connections) {
auto &c = iter.second;
//Might be better to get rid of subscriptions and just send everything and
//let the client sort out what they want idk
if (c->GetStatus() >= required_status && c->IsSubscribed(evt)) {
c->GetWebsocketConnection()->send(payload.str());
}
}
}
catch (std::exception) {
}
}
Json::Value EQ::Net::WebsocketServer::Login(WebsocketServerConnection *connection, const Json::Value &params)
{
Json::Value ret;
try {
Json::Value ret;
auto user = params[0].asString();
auto pass = params[1].asString();
auto r = _impl->login_handler(connection, user, pass);
if (r.logged_in) {
connection->SetAuthorized(true, r.account_name, r.account_id, 255);
ret["status"] = "Ok";
}
else {
connection->SetAuthorized(false, "", 0, 0);
ret["status"] = "Not Authorized";
}
return ret;
}
catch (std::exception) {
throw WebsocketException("Unable to process login request");
}
}
Json::Value EQ::Net::WebsocketServer::Subscribe(WebsocketServerConnection *connection, const Json::Value &params)
{
Json::Value ret;
try {
auto evt = params[0].asString();
connection->Subscribe(evt);
ret["status"] = "Ok";
return ret;
}
catch (std::exception) {
throw WebsocketException("Unable to process subscribe request");
}
}
Json::Value EQ::Net::WebsocketServer::Unsubscribe(WebsocketServerConnection *connection, const Json::Value &params)
{
Json::Value ret;
try {
auto evt = params[0].asString();
connection->Unsubscribe(evt);
ret["status"] = "Ok";
return ret;
}
catch (std::exception) {
throw WebsocketException("Unable to process unsubscribe request");
}
}

View File

@ -0,0 +1,63 @@
#pragma once
#include "websocket_server_connection.h"
#include "../json/json.h"
#include <memory>
#include <functional>
#include <exception>
namespace EQ
{
namespace Net
{
struct WebsocketLoginStatus
{
bool logged_in;
std::string account_name;
uint32 account_id;
int status;
};
class WebsocketException : public std::exception
{
public:
WebsocketException(const std::string &msg)
: _msg(msg.empty() ? "Unknown Error" : msg) { }
~WebsocketException() throw() {}
virtual char const *what() const throw() {
return _msg.c_str();
}
private:
const std::string _msg;
};
class WebsocketServer
{
public:
typedef std::function<Json::Value(WebsocketServerConnection*, const Json::Value&)> MethodHandler;
typedef std::function<WebsocketLoginStatus(WebsocketServerConnection*, const std::string&, const std::string&)> LoginHandler;
WebsocketServer(const std::string &addr, int port);
~WebsocketServer();
void SetMethodHandler(const std::string& method, MethodHandler handler, int required_status);
void SetLoginHandler(LoginHandler handler);
void DispatchEvent(const std::string& evt, Json::Value data = Json::Value(), int required_status = 0);
private:
void ReleaseConnection(WebsocketServerConnection *connection);
Json::Value HandleRequest(WebsocketServerConnection *connection, const std::string& method, const Json::Value &params);
Json::Value Login(WebsocketServerConnection *connection, const Json::Value &params);
Json::Value Subscribe(WebsocketServerConnection *connection, const Json::Value &params);
Json::Value Unsubscribe(WebsocketServerConnection *connection, const Json::Value &params);
struct Impl;
std::unique_ptr<Impl> _impl;
friend class WebsocketServerConnection;
};
}
}

View File

@ -0,0 +1,189 @@
#include "websocket_server_connection.h"
#include "websocket_server.h"
#include "../timer.h"
#include "../util/uuid.h"
#include <sstream>
#include <unordered_set>
#include <fmt/format.h>
struct EQ::Net::WebsocketServerConnection::Impl {
WebsocketServer *parent;
std::shared_ptr<TCPConnection> connection;
std::shared_ptr<websocket_connection> ws_connection;
std::string id;
bool authorized;
std::string account_name;
uint32 account_id;
int status;
std::unordered_set<std::string> subscribed;
};
EQ::Net::WebsocketServerConnection::WebsocketServerConnection(WebsocketServer *parent,
std::shared_ptr<TCPConnection> connection,
std::shared_ptr<websocket_connection> ws_connection)
{
_impl.reset(new Impl());
_impl->parent = parent;
_impl->connection = connection;
_impl->id = EQ::Util::UUID::Generate().ToString();
_impl->authorized = false;
_impl->account_id = 0;
_impl->status = 0;
_impl->ws_connection = ws_connection;
_impl->ws_connection->set_message_handler(std::bind(&WebsocketServerConnection::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
_impl->ws_connection->start();
connection->OnDisconnect([this](EQ::Net::TCPConnection *connection) {
_impl->parent->ReleaseConnection(this);
});
connection->OnRead([this](EQ::Net::TCPConnection *c, const unsigned char *buffer, size_t buffer_size) {
_impl->ws_connection->read_all((const char*)buffer, buffer_size);
});
connection->Start();
}
EQ::Net::WebsocketServerConnection::~WebsocketServerConnection()
{
}
std::string EQ::Net::WebsocketServerConnection::GetID() const
{
return _impl->id;
}
bool EQ::Net::WebsocketServerConnection::IsAuthorized() const
{
return _impl->authorized;
}
std::string EQ::Net::WebsocketServerConnection::GetAccountName() const
{
return _impl->account_name;
}
uint32 EQ::Net::WebsocketServerConnection::GetAccountID() const
{
return _impl->account_id;
}
int EQ::Net::WebsocketServerConnection::GetStatus() const
{
return _impl->status;
}
std::string EQ::Net::WebsocketServerConnection::RemoteIP() const
{
return _impl->connection->RemoteIP();
}
int EQ::Net::WebsocketServerConnection::RemotePort() const
{
return _impl->connection->RemotePort();
}
std::shared_ptr<EQ::Net::websocket_connection> EQ::Net::WebsocketServerConnection::GetWebsocketConnection()
{
return _impl->ws_connection;
}
std::shared_ptr<EQ::Net::TCPConnection> EQ::Net::WebsocketServerConnection::GetTCPConnection()
{
return _impl->connection;
}
void EQ::Net::WebsocketServerConnection::OnMessage(websocketpp::connection_hdl hdl, websocket_message_ptr msg)
{
BenchTimer timer;
timer.reset();
if (msg->get_opcode() == websocketpp::frame::opcode::text) {
try {
auto &payload = msg->get_payload();
std::stringstream ss(payload);
Json::Value root;
ss >> root;
auto method = root["method"].asString();
auto params = root["params"];
std::string id = "";
auto idNode = root["id"];
if (!idNode.isNull() && idNode.isString()) {
id = idNode.asString();
}
Json::Value response;
response["type"] = "method";
response["data"] = _impl->parent->HandleRequest(this, method, params);
response["method"] = method;
if(id != "") {
response["id"] = id;
}
SendResponse(response, timer.elapsed());
}
catch (std::exception &ex) {
Json::Value error;
error["type"] = "method";
error["error"] = fmt::format("{0}", ex.what());
SendResponse(error, timer.elapsed());
}
}
}
void EQ::Net::WebsocketServerConnection::SendResponse(const Json::Value &response, double time_elapsed)
{
Json::Value root = response;
root["execution_time"] = std::to_string(time_elapsed);
std::stringstream payload;
payload << root;
_impl->ws_connection->send(payload.str());
}
void EQ::Net::WebsocketServerConnection::SetAuthorized(bool v, const std::string account_name, uint32 account_id, int status)
{
_impl->authorized = v;
_impl->account_name = account_name;
_impl->account_id = account_id;
_impl->status = status;
}
void EQ::Net::WebsocketServerConnection::Subscribe(const std::string &evt)
{
if (evt == "") {
return;
}
auto iter = _impl->subscribed.find(evt);
if (iter == _impl->subscribed.end()) {
_impl->subscribed.insert(evt);
}
}
void EQ::Net::WebsocketServerConnection::Unsubscribe(const std::string &evt)
{
if (evt == "") {
return;
}
auto iter = _impl->subscribed.find(evt);
if (iter != _impl->subscribed.end()) {
_impl->subscribed.erase(iter);
}
}
bool EQ::Net::WebsocketServerConnection::IsSubscribed(const std::string &evt) const
{
auto iter = _impl->subscribed.find(evt);
if (iter != _impl->subscribed.end()) {
return true;
}
return false;
}

View File

@ -0,0 +1,49 @@
#pragma once
#include "tcp_server.h"
#include "../types.h"
#include "../json/json-forwards.h"
#include <websocketpp/config/core.hpp>
#include <websocketpp/server.hpp>
namespace EQ
{
namespace Net
{
typedef websocketpp::server<websocketpp::config::core> websocket_server;
typedef websocketpp::connection<websocketpp::config::core> websocket_connection;
typedef websocket_server::message_ptr websocket_message_ptr;
class WebsocketServer;
class WebsocketServerConnection
{
public:
WebsocketServerConnection(WebsocketServer *parent,
std::shared_ptr<TCPConnection> connection,
std::shared_ptr<websocket_connection> ws_connection);
~WebsocketServerConnection();
std::string GetID() const;
bool IsAuthorized() const;
std::string GetAccountName() const;
uint32 GetAccountID() const;
int GetStatus() const;
std::string RemoteIP() const;
int RemotePort() const;
private:
std::shared_ptr<websocket_connection> GetWebsocketConnection();
std::shared_ptr<TCPConnection> GetTCPConnection();
void OnMessage(websocketpp::connection_hdl hdl, websocket_message_ptr msg);
void SendResponse(const Json::Value &response, double time_elapsed);
void SetAuthorized(bool v, const std::string account_name, uint32 account_id, int status);
void Subscribe(const std::string &evt);
void Unsubscribe(const std::string &evt);
bool IsSubscribed(const std::string &evt) const;
struct Impl;
std::unique_ptr<Impl> _impl;
friend class WebsocketServer;
};
}
}

@ -0,0 +1 @@
Subproject commit c6d7e295bf5a0ab9b5f896720cc1a0e0fdc397a7

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

View File

@ -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);
}

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);

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));
}

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

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;
}
}