add /y/shell-exec in HTTP server
This commit is contained in:
@@ -123,6 +123,7 @@ set(SOURCES
|
|||||||
src/Server.cc
|
src/Server.cc
|
||||||
src/ServerShell.cc
|
src/ServerShell.cc
|
||||||
src/ServerState.cc
|
src/ServerState.cc
|
||||||
|
src/ShellCommands.cc
|
||||||
src/SignalWatcher.cc
|
src/SignalWatcher.cc
|
||||||
src/StaticGameData.cc
|
src/StaticGameData.cc
|
||||||
src/TeamIndex.cc
|
src/TeamIndex.cc
|
||||||
|
|||||||
@@ -678,19 +678,20 @@ The HTTP server is disabled by default, and you have to explicitly enable it in
|
|||||||
To enable the HTTP server, add a port number in the HTTPListen list in config.json. The HTTP server will listen on that port.
|
To enable the HTTP server, add a port number in the HTTPListen list in config.json. The HTTP server will listen on that port.
|
||||||
|
|
||||||
Currently, all endpoints only provide data (and hence are GET requests); there are no methods to make changes to the server state or take actions. All returned data is JSON-encoded. The HTTP server has the following endpoints:
|
Currently, all endpoints only provide data (and hence are GET requests); there are no methods to make changes to the server state or take actions. All returned data is JSON-encoded. The HTTP server has the following endpoints:
|
||||||
* `/y/data/ep3-cards`: Returns the Episode 3 card definitions.
|
* `GET /y/data/ep3-cards`: Returns the Episode 3 card definitions.
|
||||||
* `/y/data/ep3-cards-trial`: Returns the Episode 3 Trial Edition card definitions.
|
* `GET /y/data/ep3-cards-trial`: Returns the Episode 3 Trial Edition card definitions.
|
||||||
* `/y/data/common-tables`: Returns the parameters for generating common items (ItemPT files). This endpoint returns a lot of data and can be slow!
|
* `GET /y/data/common-tables`: Returns the parameters for generating common items (ItemPT files). This endpoint returns a lot of data and can be slow!
|
||||||
* `/y/data/rare-tables`: Returns a list of rare table names.
|
* `GET /y/data/rare-tables`: Returns a list of rare table names.
|
||||||
* `/y/data/rare-tables/<TABLE-NAME>` (for example, `/y/data/rare-tables/rare-table-v4`): Returns the contents of a rare item table.
|
* `GET /y/data/rare-tables/<TABLE-NAME>` (for example, `/y/data/rare-tables/rare-table-v4`): Returns the contents of a rare item table.
|
||||||
* `/y/data/config`: Returns the server's configuration file.
|
* `GET /y/data/config`: Returns the server's configuration file.
|
||||||
* `/y/clients`: Returns information about all connected clients on the game server.
|
* `GET /y/clients`: Returns information about all connected clients on the game server.
|
||||||
* `/y/proxy-clients`: Returns information about all connected clients on the proxy server.
|
* `GET /y/proxy-clients`: Returns information about all connected clients on the proxy server.
|
||||||
* `/y/lobbies`: Returns information about all lobbies and games.
|
* `GET /y/lobbies`: Returns information about all lobbies and games.
|
||||||
* `/y/server`: Returns information about the server.
|
* `GET /y/server`: Returns information about the server.
|
||||||
* `/y/rare-drops/stream`: WebSocket endpoint that sends messages whenever an announceable rare item is dropped in any game. Announceable rare items are items for which an in-game or server-wide text message is sent announcing the find.
|
* `GET /y/rare-drops/stream`: WebSocket endpoint that sends messages whenever an announceable rare item is dropped in any game. Announceable rare items are items for which an in-game or server-wide text message is sent announcing the find.
|
||||||
* `/y/summary`: Returns a summary of the server's state, connected clients, active games, and proxy sessions.
|
* `GET /y/summary`: Returns a summary of the server's state, connected clients, active games, and proxy sessions.
|
||||||
* `/y/all`: Returns everything. This endpoint can be slow!
|
* `GET /y/all`: Returns everything. This endpoint can be slow!
|
||||||
|
* `POST /y/shell-exec`: Runs a server shell command. Input should be a JSON dict of e.g. `{"command": "announce hello"}`; response will be a JSON dict of `{"result": "<result text>"}` or an HTTP error.
|
||||||
|
|
||||||
# Non-server features
|
# Non-server features
|
||||||
|
|
||||||
|
|||||||
+20
-16
@@ -291,8 +291,8 @@ phosg::JSON Account::json() const {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::print(FILE* stream) const {
|
string Account::str() const {
|
||||||
fprintf(stream, "Account: %010" PRIu32 "/%08" PRIX32 "\n", this->account_id, this->account_id);
|
std::string ret = phosg::string_printf("Account: %010" PRIu32 "/%08" PRIX32 "\n", this->account_id, this->account_id);
|
||||||
|
|
||||||
if (this->flags) {
|
if (this->flags) {
|
||||||
string flags_str = "";
|
string flags_str = "";
|
||||||
@@ -339,7 +339,7 @@ void Account::print(FILE* stream) const {
|
|||||||
} else if (phosg::ends_with(flags_str, ",")) {
|
} else if (phosg::ends_with(flags_str, ",")) {
|
||||||
flags_str.pop_back();
|
flags_str.pop_back();
|
||||||
}
|
}
|
||||||
fprintf(stream, " Flags: %08" PRIX32 " (%s)\n", this->flags, flags_str.c_str());
|
ret += phosg::string_printf(" Flags: %08" PRIX32 " (%s)\n", this->flags, flags_str.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->user_flags) {
|
if (this->user_flags) {
|
||||||
@@ -352,53 +352,57 @@ void Account::print(FILE* stream) const {
|
|||||||
} else if (phosg::ends_with(user_flags_str, ",")) {
|
} else if (phosg::ends_with(user_flags_str, ",")) {
|
||||||
user_flags_str.pop_back();
|
user_flags_str.pop_back();
|
||||||
}
|
}
|
||||||
fprintf(stream, " User flags: %08" PRIX32 " (%s)\n", this->user_flags, user_flags_str.c_str());
|
ret += phosg::string_printf(" User flags: %08" PRIX32 " (%s)\n", this->user_flags, user_flags_str.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->ban_end_time) {
|
if (this->ban_end_time) {
|
||||||
string time_str = phosg::format_time(this->ban_end_time);
|
string time_str = phosg::format_time(this->ban_end_time);
|
||||||
fprintf(stream, " Banned until: %" PRIu64 " (%s)\n", this->ban_end_time, time_str.c_str());
|
ret += phosg::string_printf(" Banned until: %" PRIu64 " (%s)\n", this->ban_end_time, time_str.c_str());
|
||||||
}
|
}
|
||||||
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
|
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
|
||||||
fprintf(stream, " Episode 3 meseta: %" PRIu32 " (total earned: %" PRIu32 ")\n", this->ep3_current_meseta, this->ep3_total_meseta_earned);
|
ret += phosg::string_printf(" Episode 3 meseta: %" PRIu32 " (total earned: %" PRIu32 ")\n",
|
||||||
|
this->ep3_current_meseta, this->ep3_total_meseta_earned);
|
||||||
}
|
}
|
||||||
if (!this->last_player_name.empty()) {
|
if (!this->last_player_name.empty()) {
|
||||||
fprintf(stream, " Last player name: \"%s\"\n", this->last_player_name.c_str());
|
ret += phosg::string_printf(" Last player name: \"%s\"\n", this->last_player_name.c_str());
|
||||||
}
|
}
|
||||||
if (!this->auto_reply_message.empty()) {
|
if (!this->auto_reply_message.empty()) {
|
||||||
fprintf(stream, " Auto reply message: \"%s\"\n", this->auto_reply_message.c_str());
|
ret += phosg::string_printf(" Auto reply message: \"%s\"\n", this->auto_reply_message.c_str());
|
||||||
}
|
}
|
||||||
if (this->bb_team_id) {
|
if (this->bb_team_id) {
|
||||||
fprintf(stream, " BB team ID: %08" PRIX32 "\n", this->bb_team_id);
|
ret += phosg::string_printf(" BB team ID: %08" PRIX32 "\n", this->bb_team_id);
|
||||||
}
|
}
|
||||||
if (this->is_temporary) {
|
if (this->is_temporary) {
|
||||||
fprintf(stream, " Is temporary license: true\n");
|
ret += phosg::string_printf(" Is temporary license: true\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& it : this->dc_nte_licenses) {
|
for (const auto& it : this->dc_nte_licenses) {
|
||||||
fprintf(stream, " DC NTE license: serial_number=%s access_key=%s\n",
|
ret += phosg::string_printf(" DC NTE license: serial_number=%s access_key=%s\n",
|
||||||
it.second->serial_number.c_str(), it.second->access_key.c_str());
|
it.second->serial_number.c_str(), it.second->access_key.c_str());
|
||||||
}
|
}
|
||||||
for (const auto& it : this->dc_licenses) {
|
for (const auto& it : this->dc_licenses) {
|
||||||
fprintf(stream, " DC license: serial_number=%" PRIX32 " access_key=%s\n",
|
ret += phosg::string_printf(" DC license: serial_number=%" PRIX32 " access_key=%s\n",
|
||||||
it.second->serial_number, it.second->access_key.c_str());
|
it.second->serial_number, it.second->access_key.c_str());
|
||||||
}
|
}
|
||||||
for (const auto& it : this->pc_licenses) {
|
for (const auto& it : this->pc_licenses) {
|
||||||
fprintf(stream, " PC license: serial_number=%" PRIX32 " access_key=%s\n",
|
ret += phosg::string_printf(" PC license: serial_number=%" PRIX32 " access_key=%s\n",
|
||||||
it.second->serial_number, it.second->access_key.c_str());
|
it.second->serial_number, it.second->access_key.c_str());
|
||||||
}
|
}
|
||||||
for (const auto& it : this->gc_licenses) {
|
for (const auto& it : this->gc_licenses) {
|
||||||
fprintf(stream, " GC license: serial_number=%010" PRIu32 " access_key=%s password=%s\n",
|
ret += phosg::string_printf(" GC license: serial_number=%010" PRIu32 " access_key=%s password=%s\n",
|
||||||
it.second->serial_number, it.second->access_key.c_str(), it.second->password.c_str());
|
it.second->serial_number, it.second->access_key.c_str(), it.second->password.c_str());
|
||||||
}
|
}
|
||||||
for (const auto& it : this->xb_licenses) {
|
for (const auto& it : this->xb_licenses) {
|
||||||
fprintf(stream, " XB license: gamertag=%s user_id=%016" PRIX64 " account_id=%016" PRIX64 "\n",
|
ret += phosg::string_printf(" XB license: gamertag=%s user_id=%016" PRIX64 " account_id=%016" PRIX64 "\n",
|
||||||
it.second->gamertag.c_str(), it.second->user_id, it.second->account_id);
|
it.second->gamertag.c_str(), it.second->user_id, it.second->account_id);
|
||||||
}
|
}
|
||||||
for (const auto& it : this->bb_licenses) {
|
for (const auto& it : this->bb_licenses) {
|
||||||
fprintf(stream, " BB license: username=%s password=%s\n",
|
ret += phosg::string_printf(" BB license: username=%s password=%s\n",
|
||||||
it.second->username.c_str(), it.second->password.c_str());
|
it.second->username.c_str(), it.second->password.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
phosg::strip_trailing_whitespace(ret);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::save() const {
|
void Account::save() const {
|
||||||
|
|||||||
+1
-1
@@ -162,7 +162,7 @@ struct Account {
|
|||||||
this->user_flags ^= static_cast<uint32_t>(flag);
|
this->user_flags ^= static_cast<uint32_t>(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print(FILE* stream) const;
|
std::string str() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Login {
|
struct Login {
|
||||||
|
|||||||
+32
-27
@@ -717,66 +717,71 @@ void Tournament::send_all_state_updates_on_deletion() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tournament::print_bracket(FILE* stream) const {
|
string Tournament::bracket_str() const {
|
||||||
function<void(shared_ptr<Match>, size_t)> print_match = [&](shared_ptr<Match> m, size_t indent_level) -> void {
|
string ret = phosg::string_printf("Tournament \"%s\"\n", this->name.c_str());
|
||||||
for (size_t z = 0; z < indent_level; z++) {
|
|
||||||
fputc(' ', stream);
|
function<void(shared_ptr<Match>, size_t)> add_match = [&](shared_ptr<Match> m, size_t indent_level) -> void {
|
||||||
fputc(' ', stream);
|
ret.append(2 * indent_level, ' ');
|
||||||
|
ret += m->str();
|
||||||
|
if (this->pending_matches.count(m)) {
|
||||||
|
ret += " (PENDING)";
|
||||||
}
|
}
|
||||||
string match_str = m->str();
|
ret.push_back('\n');
|
||||||
fprintf(stream, "%s%s\n", match_str.c_str(), this->pending_matches.count(m) ? " (PENDING)" : "");
|
|
||||||
if (m->preceding_a) {
|
if (m->preceding_a) {
|
||||||
print_match(m->preceding_a, indent_level + 1);
|
add_match(m->preceding_a, indent_level + 1);
|
||||||
}
|
}
|
||||||
if (m->preceding_b) {
|
if (m->preceding_b) {
|
||||||
print_match(m->preceding_b, indent_level + 1);
|
add_match(m->preceding_b, indent_level + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fprintf(stream, "Tournament \"%s\"\n", this->name.c_str());
|
|
||||||
auto en_vm = this->map->version(1);
|
auto en_vm = this->map->version(1);
|
||||||
if (en_vm) {
|
if (en_vm) {
|
||||||
string map_name = en_vm->map->name.decode(en_vm->language);
|
string map_name = en_vm->map->name.decode(en_vm->language);
|
||||||
fprintf(stream, " Map: %08" PRIX32 " (%s)\n", this->map->map_number, map_name.c_str());
|
ret += phosg::string_printf(" Map: %08" PRIX32 " (%s)\n", this->map->map_number, map_name.c_str());
|
||||||
} else {
|
} else {
|
||||||
fprintf(stream, " Map: %08" PRIX32 "\n", this->map->map_number);
|
ret += phosg::string_printf(" Map: %08" PRIX32 "\n", this->map->map_number);
|
||||||
}
|
}
|
||||||
string rules_str = this->rules.str();
|
string rules_str = this->rules.str();
|
||||||
fprintf(stream, " Rules: %s\n", rules_str.c_str());
|
ret += phosg::string_printf(" Rules: %s\n", rules_str.c_str());
|
||||||
fprintf(stream, " Structure: %s, %zu entries\n", (this->flags & Flag::IS_2V2) ? "2v2" : "1v1", this->num_teams);
|
ret += phosg::string_printf(" Structure: %s, %zu entries\n", (this->flags & Flag::IS_2V2) ? "2v2" : "1v1", this->num_teams);
|
||||||
fprintf(stream, " COM teams: %s\n", (this->flags & Flag::HAS_COM_TEAMS) ? "allowed" : "forbidden");
|
ret += phosg::string_printf(" COM teams: %s\n", (this->flags & Flag::HAS_COM_TEAMS) ? "allowed" : "forbidden");
|
||||||
fprintf(stream, " Shuffle entries: %s\n", (this->flags & Flag::SHUFFLE_ENTRIES) ? "yes" : "no");
|
ret += phosg::string_printf(" Shuffle entries: %s\n", (this->flags & Flag::SHUFFLE_ENTRIES) ? "yes" : "no");
|
||||||
fprintf(stream, " Resize on start: %s\n", (this->flags & Flag::RESIZE_ON_START) ? "yes" : "no");
|
ret += phosg::string_printf(" Resize on start: %s\n", (this->flags & Flag::RESIZE_ON_START) ? "yes" : "no");
|
||||||
switch (this->current_state) {
|
switch (this->current_state) {
|
||||||
case State::REGISTRATION:
|
case State::REGISTRATION:
|
||||||
fprintf(stream, " State: REGISTRATION\n");
|
ret += " State: REGISTRATION\n";
|
||||||
break;
|
break;
|
||||||
case State::IN_PROGRESS:
|
case State::IN_PROGRESS:
|
||||||
fprintf(stream, " State: IN_PROGRESS\n");
|
ret += " State: IN_PROGRESS\n";
|
||||||
break;
|
break;
|
||||||
case State::COMPLETE:
|
case State::COMPLETE:
|
||||||
fprintf(stream, " State: COMPLETE\n");
|
ret += " State: COMPLETE\n";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stream, " State: UNKNOWN\n");
|
ret += " State: UNKNOWN\n";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this->final_match) {
|
if (this->final_match) {
|
||||||
fprintf(stream, " Standings:\n");
|
ret += " Standings:\n";
|
||||||
print_match(this->final_match, 2);
|
add_match(this->final_match, 2);
|
||||||
}
|
}
|
||||||
if (this->current_state == State::REGISTRATION) {
|
if (this->current_state == State::REGISTRATION) {
|
||||||
fprintf(stream, " Teams:\n");
|
ret += " Teams:\n";
|
||||||
for (const auto& team : this->teams) {
|
for (const auto& team : this->teams) {
|
||||||
string team_str = team->str();
|
string team_str = team->str();
|
||||||
fprintf(stream, " %s\n", team_str.c_str());
|
ret += phosg::string_printf(" %s\n", team_str.c_str());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fprintf(stream, " Pending matches:\n");
|
ret += " Pending matches:\n";
|
||||||
for (const auto& match : this->pending_matches) {
|
for (const auto& match : this->pending_matches) {
|
||||||
string match_str = match->str();
|
string match_str = match->str();
|
||||||
fprintf(stream, " %s\n", match_str.c_str());
|
ret += phosg::string_printf(" %s\n", match_str.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
phosg::strip_trailing_whitespace(ret);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
TournamentIndex::TournamentIndex(
|
TournamentIndex::TournamentIndex(
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ public:
|
|||||||
void send_all_state_updates() const;
|
void send_all_state_updates() const;
|
||||||
void send_all_state_updates_on_deletion() const;
|
void send_all_state_updates_on_deletion() const;
|
||||||
|
|
||||||
void print_bracket(FILE* stream) const;
|
std::string bracket_str() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void create_bracket_matches();
|
void create_bracket_matches();
|
||||||
|
|||||||
+30
-11
@@ -1,5 +1,6 @@
|
|||||||
#include "EventUtils.hh"
|
#include "EventUtils.hh"
|
||||||
|
|
||||||
|
#include <event2/buffer.h>
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
@@ -7,37 +8,55 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
static void dispatch_forward_to_event_thread(evutil_socket_t, short, void* ctx) {
|
static void dispatch_forward_to_event_thread(evutil_socket_t, short, void* ctx) {
|
||||||
auto* fn = reinterpret_cast<std::function<void()>*>(ctx);
|
auto* fn = reinterpret_cast<function<void()>*>(ctx);
|
||||||
(*fn)();
|
(*fn)();
|
||||||
delete fn;
|
delete fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void forward_to_event_thread(std::shared_ptr<struct event_base> base, std::function<void()>&& fn) {
|
void forward_to_event_thread(shared_ptr<struct event_base> base, function<void()>&& fn) {
|
||||||
struct timeval tv = {0, 0};
|
struct timeval tv = {0, 0};
|
||||||
std::function<void()>* new_fn = new std::function<void()>(std::move(fn));
|
function<void()>* new_fn = new function<void()>(std::move(fn));
|
||||||
event_base_once(base.get(), -1, EV_TIMEOUT, dispatch_forward_to_event_thread, new_fn, &tv);
|
event_base_once(base.get(), -1, EV_TIMEOUT, dispatch_forward_to_event_thread, new_fn, &tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
void call_on_event_thread<void>(std::shared_ptr<struct event_base> base, std::function<void()>&& compute) {
|
void call_on_event_thread<void>(shared_ptr<struct event_base> base, function<void()>&& compute) {
|
||||||
bool succeeded = false;
|
bool succeeded = false;
|
||||||
std::string exc_what;
|
string exc_what;
|
||||||
std::mutex ret_lock;
|
mutex ret_lock;
|
||||||
std::condition_variable ret_cv;
|
condition_variable ret_cv;
|
||||||
std::unique_lock<std::mutex> g(ret_lock);
|
unique_lock<mutex> g(ret_lock);
|
||||||
forward_to_event_thread(base, [&]() -> void {
|
forward_to_event_thread(base, [&]() -> void {
|
||||||
std::lock_guard<std::mutex> g(ret_lock);
|
lock_guard<mutex> g(ret_lock);
|
||||||
try {
|
try {
|
||||||
compute();
|
compute();
|
||||||
succeeded = true;
|
succeeded = true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const exception& e) {
|
||||||
exc_what = e.what();
|
exc_what = e.what();
|
||||||
}
|
}
|
||||||
ret_cv.notify_one();
|
ret_cv.notify_one();
|
||||||
});
|
});
|
||||||
ret_cv.wait(g);
|
ret_cv.wait(g);
|
||||||
if (!succeeded) {
|
if (!succeeded) {
|
||||||
throw std::runtime_error(exc_what);
|
throw runtime_error(exc_what);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string evbuffer_remove_str(struct evbuffer* buf, ssize_t size) {
|
||||||
|
if (!buf) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (size < 0) {
|
||||||
|
size = static_cast<size_t>(evbuffer_get_length(buf));
|
||||||
|
}
|
||||||
|
string ret(size, '\0');
|
||||||
|
ssize_t bytes_removed = evbuffer_remove(buf, ret.data(), ret.size());
|
||||||
|
if (bytes_removed < 0) {
|
||||||
|
throw std::runtime_error("can\'t remove data from buffer");
|
||||||
|
}
|
||||||
|
ret.resize(bytes_removed);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
// Calls a function on the given base's event thread. This function returns
|
// Calls a function on the given base's event thread. This function returns
|
||||||
// when the call has been enqueued, not necessarily after it returns.
|
// when the call has been enqueued, not necessarily after it returns.
|
||||||
@@ -40,3 +41,5 @@ T call_on_event_thread(std::shared_ptr<struct event_base> base, std::function<T(
|
|||||||
|
|
||||||
template <>
|
template <>
|
||||||
void call_on_event_thread<void>(std::shared_ptr<struct event_base> base, std::function<void()>&& compute);
|
void call_on_event_thread<void>(std::shared_ptr<struct event_base> base, std::function<void()>&& compute);
|
||||||
|
|
||||||
|
std::string evbuffer_remove_str(struct evbuffer* buf, ssize_t size = -1);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
#include "ProxyServer.hh"
|
#include "ProxyServer.hh"
|
||||||
#include "Server.hh"
|
#include "Server.hh"
|
||||||
|
#include "ShellCommands.hh"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@@ -1154,6 +1155,25 @@ phosg::JSON HTTPServer::generate_rare_table_json(const std::string& table_name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HTTPServer::require_GET(struct evhttp_request* req) {
|
||||||
|
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) {
|
||||||
|
throw HTTPServer::http_error(405, "GET method required for this endpoint");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
phosg::JSON HTTPServer::require_POST(struct evhttp_request* req) {
|
||||||
|
if (evhttp_request_get_command(req) != EVHTTP_REQ_POST) {
|
||||||
|
throw HTTPServer::http_error(405, "POST method required for this endpoint");
|
||||||
|
}
|
||||||
|
const evkeyvalq* headers = evhttp_request_get_input_headers(req);
|
||||||
|
const char* content_type = evhttp_find_header(headers, "Content-Type");
|
||||||
|
if (!content_type || strcmp(content_type, "application/json")) {
|
||||||
|
throw HTTPServer::http_error(400, "POST requests must use the application/json content type");
|
||||||
|
}
|
||||||
|
struct evbuffer* in_buf = evhttp_request_get_input_buffer(req);
|
||||||
|
return phosg::JSON::parse(evbuffer_remove_str(in_buf));
|
||||||
|
}
|
||||||
|
|
||||||
void HTTPServer::handle_request(struct evhttp_request* req) {
|
void HTTPServer::handle_request(struct evhttp_request* req) {
|
||||||
shared_ptr<const phosg::JSON> ret;
|
shared_ptr<const phosg::JSON> ret;
|
||||||
uint32_t serialize_options = 0;
|
uint32_t serialize_options = 0;
|
||||||
@@ -1177,6 +1197,7 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uri == "/") {
|
if (uri == "/") {
|
||||||
|
this->require_GET(req);
|
||||||
auto endpoints_json = phosg::JSON::list({
|
auto endpoints_json = phosg::JSON::list({
|
||||||
"/y/data/ep3-cards",
|
"/y/data/ep3-cards",
|
||||||
"/y/data/ep3-cards-trial",
|
"/y/data/ep3-cards-trial",
|
||||||
@@ -1191,10 +1212,22 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
|||||||
"/y/rare-drops/stream",
|
"/y/rare-drops/stream",
|
||||||
"/y/summary",
|
"/y/summary",
|
||||||
"/y/all",
|
"/y/all",
|
||||||
|
"/y/shell-exec",
|
||||||
});
|
});
|
||||||
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"endpoints", std::move(endpoints_json)}}));
|
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"endpoints", std::move(endpoints_json)}}));
|
||||||
|
|
||||||
|
} else if (uri == "/y/shell-exec") {
|
||||||
|
auto json = this->require_POST(req);
|
||||||
|
auto command = json.get_string("command");
|
||||||
|
try {
|
||||||
|
ret = make_shared<phosg::JSON>(phosg::JSON::dict(
|
||||||
|
{{"result", phosg::join(ShellCommand::dispatch_str(this->state, command), "\n")}}));
|
||||||
|
} catch (const exception& e) {
|
||||||
|
throw http_error(400, e.what());
|
||||||
|
}
|
||||||
|
|
||||||
} else if (uri == "/y/rare-drops/stream") {
|
} else if (uri == "/y/rare-drops/stream") {
|
||||||
|
this->require_GET(req);
|
||||||
auto c = this->enable_websockets(req);
|
auto c = this->enable_websockets(req);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
throw http_error(400, "this path requires a websocket connection");
|
throw http_error(400, "this path requires a websocket connection");
|
||||||
@@ -1206,28 +1239,40 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (uri == "/y/data/ep3-cards") {
|
} else if (uri == "/y/data/ep3-cards") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_ep3_cards_json(false));
|
ret = make_shared<phosg::JSON>(this->generate_ep3_cards_json(false));
|
||||||
} else if (uri == "/y/data/ep3-cards-trial") {
|
} else if (uri == "/y/data/ep3-cards-trial") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_ep3_cards_json(true));
|
ret = make_shared<phosg::JSON>(this->generate_ep3_cards_json(true));
|
||||||
} else if (uri == "/y/data/common-tables") {
|
} else if (uri == "/y/data/common-tables") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_common_tables_json());
|
ret = make_shared<phosg::JSON>(this->generate_common_tables_json());
|
||||||
} else if (uri == "/y/data/rare-tables") {
|
} else if (uri == "/y/data/rare-tables") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_rare_tables_json());
|
ret = make_shared<phosg::JSON>(this->generate_rare_tables_json());
|
||||||
} else if (!strncmp(uri.c_str(), "/y/data/rare-tables/", 20)) {
|
} else if (!strncmp(uri.c_str(), "/y/data/rare-tables/", 20)) {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_rare_table_json(uri.substr(20)));
|
ret = make_shared<phosg::JSON>(this->generate_rare_table_json(uri.substr(20)));
|
||||||
} else if (uri == "/y/data/config") {
|
} else if (uri == "/y/data/config") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = call_on_event_thread<shared_ptr<const phosg::JSON>>(this->state->base, [this]() { return this->state->config_json; });
|
ret = call_on_event_thread<shared_ptr<const phosg::JSON>>(this->state->base, [this]() { return this->state->config_json; });
|
||||||
} else if (uri == "/y/clients") {
|
} else if (uri == "/y/clients") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_game_server_clients_json());
|
ret = make_shared<phosg::JSON>(this->generate_game_server_clients_json());
|
||||||
} else if (uri == "/y/proxy-clients") {
|
} else if (uri == "/y/proxy-clients") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_proxy_server_clients_json());
|
ret = make_shared<phosg::JSON>(this->generate_proxy_server_clients_json());
|
||||||
} else if (uri == "/y/lobbies") {
|
} else if (uri == "/y/lobbies") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_lobbies_json());
|
ret = make_shared<phosg::JSON>(this->generate_lobbies_json());
|
||||||
} else if (uri == "/y/server") {
|
} else if (uri == "/y/server") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_server_info_json());
|
ret = make_shared<phosg::JSON>(this->generate_server_info_json());
|
||||||
} else if (uri == "/y/summary") {
|
} else if (uri == "/y/summary") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_summary_json());
|
ret = make_shared<phosg::JSON>(this->generate_summary_json());
|
||||||
} else if (uri == "/y/all") {
|
} else if (uri == "/y/all") {
|
||||||
|
this->require_GET(req);
|
||||||
ret = make_shared<phosg::JSON>(this->generate_all_json());
|
ret = make_shared<phosg::JSON>(this->generate_all_json());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -1248,6 +1293,10 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
throw logic_error("ret was not set after HTTP handler completed");
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t handler_end = phosg::now();
|
uint64_t handler_end = phosg::now();
|
||||||
unique_ptr<struct evbuffer, void (*)(struct evbuffer*)> out_buffer(evbuffer_new(), evbuffer_free);
|
unique_ptr<struct evbuffer, void (*)(struct evbuffer*)> out_buffer(evbuffer_new(), evbuffer_free);
|
||||||
string* serialized = new string(ret->serialize(phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | serialize_options));
|
string* serialized = new string(ret->serialize(phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | serialize_options));
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ protected:
|
|||||||
|
|
||||||
std::unordered_map<struct bufferevent*, std::shared_ptr<WebsocketClient>> bev_to_websocket_client;
|
std::unordered_map<struct bufferevent*, std::shared_ptr<WebsocketClient>> bev_to_websocket_client;
|
||||||
|
|
||||||
|
static void require_GET(struct evhttp_request* req);
|
||||||
|
static phosg::JSON require_POST(struct evhttp_request* req);
|
||||||
|
|
||||||
std::shared_ptr<WebsocketClient> enable_websockets(struct evhttp_request* req);
|
std::shared_ptr<WebsocketClient> enable_websockets(struct evhttp_request* req);
|
||||||
|
|
||||||
static void dispatch_on_websocket_read(struct bufferevent* bev, void* ctx);
|
static void dispatch_on_websocket_read(struct bufferevent* bev, void* ctx);
|
||||||
|
|||||||
@@ -1763,7 +1763,6 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto tourn = l->tournament_match->tournament.lock();
|
auto tourn = l->tournament_match->tournament.lock();
|
||||||
tourn->print_bracket(stderr);
|
|
||||||
|
|
||||||
shared_ptr<Episode3::Tournament::Team> winner_team;
|
shared_ptr<Episode3::Tournament::Team> winner_team;
|
||||||
shared_ptr<Episode3::Tournament::Team> loser_team;
|
shared_ptr<Episode3::Tournament::Team> loser_team;
|
||||||
@@ -1785,7 +1784,7 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
meseta_reward = (l->tournament_match->round_num - 1 < round_rewards.size())
|
meseta_reward = (l->tournament_match->round_num - 1 < round_rewards.size())
|
||||||
? round_rewards[l->tournament_match->round_num - 1]
|
? round_rewards[l->tournament_match->round_num - 1]
|
||||||
: round_rewards.back();
|
: round_rewards.back();
|
||||||
if (l->tournament_match == tourn->get_final_match()) {
|
if (tourn && (l->tournament_match == tourn->get_final_match())) {
|
||||||
meseta_reward += s->ep3_final_round_meseta_bonus;
|
meseta_reward += s->ep3_final_round_meseta_bonus;
|
||||||
}
|
}
|
||||||
for (const auto& player : winner_team->players) {
|
for (const auto& player : winner_team->players) {
|
||||||
@@ -1803,7 +1802,9 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
}
|
}
|
||||||
send_ep3_tournament_match_result(l, meseta_reward);
|
send_ep3_tournament_match_result(l, meseta_reward);
|
||||||
|
|
||||||
on_tournament_bracket_updated(s, tourn);
|
if (tourn) {
|
||||||
|
on_tournament_bracket_updated(s, tourn);
|
||||||
|
}
|
||||||
l->ep3_server->tournament_match_result_sent = true;
|
l->ep3_server->tournament_match_result_sent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1168
File diff suppressed because it is too large
Load Diff
@@ -10,12 +10,6 @@
|
|||||||
|
|
||||||
class ServerShell : public std::enable_shared_from_this<ServerShell> {
|
class ServerShell : public std::enable_shared_from_this<ServerShell> {
|
||||||
public:
|
public:
|
||||||
class exit_shell : public std::runtime_error {
|
|
||||||
public:
|
|
||||||
exit_shell();
|
|
||||||
~exit_shell() = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit ServerShell(std::shared_ptr<ServerState> state);
|
explicit ServerShell(std::shared_ptr<ServerState> state);
|
||||||
ServerShell(const ServerShell&) = delete;
|
ServerShell(const ServerShell&) = delete;
|
||||||
ServerShell(ServerShell&&) = delete;
|
ServerShell(ServerShell&&) = delete;
|
||||||
@@ -25,8 +19,6 @@ public:
|
|||||||
|
|
||||||
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name);
|
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session(const std::string& name);
|
||||||
|
|
||||||
void execute_command(const std::string& command);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<ServerState> state;
|
std::shared_ptr<ServerState> state;
|
||||||
std::thread th;
|
std::thread th;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ProxyServer.hh"
|
||||||
|
#include "ServerState.hh"
|
||||||
|
|
||||||
|
class exit_shell : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
exit_shell();
|
||||||
|
~exit_shell() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShellCommand {
|
||||||
|
struct Args {
|
||||||
|
std::shared_ptr<ServerState> s;
|
||||||
|
std::string command;
|
||||||
|
std::string args;
|
||||||
|
std::string session_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* name;
|
||||||
|
const char* help_text;
|
||||||
|
bool run_on_event_thread;
|
||||||
|
std::deque<std::string> (*run)(Args&);
|
||||||
|
|
||||||
|
static std::vector<const ShellCommand*> commands_by_order;
|
||||||
|
static std::unordered_map<std::string, const ShellCommand*> commands_by_name;
|
||||||
|
|
||||||
|
ShellCommand(const char* name, const char* help_text, bool run_on_event_thread, std::deque<std::string> (*run)(Args&));
|
||||||
|
|
||||||
|
static std::deque<std::string> dispatch_str(std::shared_ptr<ServerState> s, const std::string& command);
|
||||||
|
static std::deque<std::string> dispatch(Args& args);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user