parselink-old/source/server.cpp
Kurt Sassenrath ec3b953384 Various updates to initial server code.
Need to create user sessions and handle msgpack-encoded packets. The
plan is to use a branch of oh::msgpack with tl::expected.
2023-09-05 16:25:57 -07:00

170 lines
5.4 KiB
C++

//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: Server
//
// Server implementation. Currently, a monolithic server which:
// * Communicates with users via TCP (msgpack).
// * Runs the websocket server for overlays to read.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include <logging.h>
#include <server.h>
#include <boost/asio/io_context.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/as_tuple.hpp>
using namespace parselink;
namespace net = boost::asio;
using net::co_spawn;
using net::awaitable;
using net::use_awaitable;
using net::deferred;
using net::detached;
//-----------------------------------------------------------------------------
// TODO(ksassenrath): These are logging formatters for various boost/asio types.
// Not all code is exposed to them, so they cannot be defined inside the
// generic logging/formatters.h header. They should go somewhere else.
//-----------------------------------------------------------------------------
template <>
struct parselink::logging::theme<boost::system::error_code>
: parselink::logging::static_theme<fmt::color::fire_brick> {};
template <>
struct fmt::formatter<boost::system::error_code>
: fmt::formatter<std::string_view> {
template<typename FormatContext>
constexpr auto format(auto const& v, FormatContext& ctx) const {
return fmt::formatter<std::string_view>::format(v.message(), ctx);
}
};
template <typename T>
concept endpoint = requires(T const& t) {
{t.address()};
{t.port()};
};
template <endpoint T>
struct parselink::logging::theme<T>
: parselink::logging::static_theme<fmt::color::coral> {};
template <endpoint T>
struct fmt::formatter<T>
: fmt::formatter<std::string_view> {
template<typename FormatContext>
constexpr auto format(auto const& v, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{}:{}", v.address().to_string(),
v.port());
}
};
//-----------------------------------------------------------------------------
// End formatters
//-----------------------------------------------------------------------------
namespace {
logging::logger logger("server");
constexpr auto no_ex_coro = net::as_tuple(use_awaitable);
constexpr auto no_ex_defer = net::as_tuple(deferred);
}
class monolithic_server : public server {
public:
monolithic_server(std::string_view address, std::uint16_t user_port,
std::uint16_t websocket_port);
std::error_code run() noexcept override;
private:
awaitable<void> echo(net::ip::tcp::socket socket);
awaitable<void> user_listen();
net::io_context io_context_;
net::ip::address addr_;
std::uint16_t user_port_;
std::uint16_t websocket_port_;
};
monolithic_server::monolithic_server(std::string_view address,
std::uint16_t user_port, std::uint16_t websocket_port)
: io_context_{1}
, addr_(net::ip::address::from_string(std::string{address}))
, user_port_{user_port}
, websocket_port_{websocket_port} {
logger.debug("Creating monolithic_server(address = {}, user_port = {}, "
"websocket_port = {})", address, user_port_, websocket_port_);
}
awaitable<void> monolithic_server::echo(net::ip::tcp::socket socket) {
std::array<std::byte, 4096> buffer;
while (true) {
auto [ec, n] = co_await socket.async_read_some(net::buffer(buffer), no_ex_coro);
if (ec) {
logger.error("Read from socket failed: {}", ec);
co_return;
}
auto [ec2, x] = co_await net::async_write(socket, net::buffer(buffer, n), no_ex_coro);
if (ec2) {
logger.error("Write to socket failed: {}", ec);
co_return;
}
}
}
awaitable<void> monolithic_server::user_listen() {
auto exec = co_await net::this_coro::executor;
net::ip::tcp::acceptor acceptor{exec, {addr_, user_port_}};
while (true) {
auto socket = co_await acceptor.async_accept(use_awaitable);
logger.debug("Accepted new connection from {}", socket.remote_endpoint());
co_spawn(exec, echo(std::move(socket)), detached);
}
}
std::error_code monolithic_server::run() noexcept {
logger.info("Starting server.");
net::signal_set signals(io_context_, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){
logger.info("Received signal... Shutting down.");
io_context_.stop();
});
co_spawn(io_context_, user_listen(), detached);
io_context_.run();
return {};
}
std::unique_ptr<server> parselink::make_server(std::string_view address,
std::uint16_t user_port, std::uint16_t websocket_port) {
using impl = monolithic_server;
return std::make_unique<impl>(address, user_port, websocket_port);
}