diff --git a/src/common/aliases.hpp b/src/common/aliases.hpp new file mode 100644 index 0000000..04cfb14 --- /dev/null +++ b/src/common/aliases.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +// --- Error handling --- +using ErrorCode = boost::system::error_code; + +// --- IO Context --- +using IoContext = boost::asio::io_context; + +// --- TCP --- +using TcpSocket = boost::asio::ip::tcp::socket; +using TcpAcceptor = boost::asio::ip::tcp::acceptor; +using TcpEndpoint = boost::asio::ip::tcp::endpoint; + +// --- UDP (для DNS) --- +using UdpSocket = boost::asio::ip::udp::socket; +using UdpEndpoint = boost::asio::ip::udp::endpoint; + +// --- Таймеры --- +using SteadyTimer = boost::asio::steady_timer; + +// --- Program options --- +using OptionsDescription = boost::program_options::options_description; +using VariablesMap = boost::program_options::variables_map; diff --git a/src/common/cli_base.cpp b/src/common/cli_base.cpp new file mode 100644 index 0000000..cee56ec --- /dev/null +++ b/src/common/cli_base.cpp @@ -0,0 +1,34 @@ +#include "cli_base.hpp" +#include +#include + +CliBase::CliBase(const std::string &title) + : desc_(title) + , help_(false) +{ +} + +void CliBase::parse(int argc, char *argv[]) +{ + using namespace boost::program_options; + + setupOptions(); // must be first + + parsed_options parsed = parse_command_line(argc, argv, desc_); + store(parsed, vm_); + notify(vm_); + + if (vm_.count("help")) { + help_ = true; + } +} + +bool CliBase::isHelp() const +{ + return help_; +} + +void CliBase::printHelp() const +{ + std::cout << desc_ << "\n"; +} diff --git a/src/common/cli_base.hpp b/src/common/cli_base.hpp new file mode 100644 index 0000000..d64e5df --- /dev/null +++ b/src/common/cli_base.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +class CliBase +{ + public: + CliBase(const std::string &title); + + virtual ~CliBase() = default; + + // parse argc/argv + void parse(int argc, char *argv[]); + + bool isHelp() const; + void printHelp() const; + + protected: + // children add their specific options here + virtual void setupOptions() = 0; + + boost::program_options::options_description desc_; + boost::program_options::variables_map vm_; + bool help_; +}; diff --git a/src/common/logger.cpp b/src/common/logger.cpp new file mode 100644 index 0000000..8808e31 --- /dev/null +++ b/src/common/logger.cpp @@ -0,0 +1,63 @@ +#include "logger.hpp" + +#include + +void Logger::init(bool toConsole, const std::string_view filePath) +{ + using namespace boost::log; + using namespace boost::posix_time; + + add_common_attributes(); + + if (toConsole) { + add_console_log( + std::clog, + keywords::format = + (expressions::stream + << "[" + << expressions::attr("TimeStamp") + << "] " + << "<" << trivial::severity << "> " << expressions::smessage) + ); + } + + if (!filePath.empty()) { + add_file_log( + keywords::file_name = filePath, + keywords::auto_flush = true, + keywords::format = + (expressions::stream + << "[" + << expressions::attr("TimeStamp") + << "] " + << "<" << trivial::severity << "> " << expressions::smessage) + ); + } + + core::get()->set_filter(trivial::severity >= trivial::info); +} + +void Logger::info(const std::string_view msg) +{ + BOOST_LOG_TRIVIAL(info) << msg; +} + +void Logger::warn(const std::string_view msg) +{ + BOOST_LOG_TRIVIAL(warning) << msg; +} + +void Logger::error(const std::string_view msg) +{ + BOOST_LOG_TRIVIAL(error) << msg; +} + +void Logger::debug(const std::string_view msg) +{ + BOOST_LOG_TRIVIAL(debug) << msg; +} + +void Logger::fatal(const std::string_view msg) +{ + BOOST_LOG_TRIVIAL(fatal) << msg; +} diff --git a/src/common/logger.hpp b/src/common/logger.hpp new file mode 100644 index 0000000..d10e071 --- /dev/null +++ b/src/common/logger.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class Logger +{ + public: + static void + init(bool toConsole = true, const std::string_view filePath = ""); + + static void info(const std::string_view msg); + static void warn(const std::string_view msg); + static void error(const std::string_view msg); + static void debug(const std::string_view msg); + static void fatal(const std::string_view msg); +}; diff --git a/src/common/server.hpp b/src/common/server.hpp new file mode 100644 index 0000000..60a60a1 --- /dev/null +++ b/src/common/server.hpp @@ -0,0 +1,9 @@ +#pragma once + +class Server +{ + public: + virtual ~Server() = default; + virtual void start() = 0; + virtual void stop() = 0; +}; diff --git a/src/common/session_base.cpp b/src/common/session_base.cpp new file mode 100644 index 0000000..ac70884 --- /dev/null +++ b/src/common/session_base.cpp @@ -0,0 +1,70 @@ +#include "session_base.hpp" +#include "logger.hpp" +#include + +SessionBase::SessionBase(TcpSocket socket) + : socket_(std::move(socket)) +{ +} + +void SessionBase::start() +{ + doRead(); +} + +void SessionBase::doRead() +{ + using namespace boost::asio; + + auto self = shared_from_this(); + + auto handler = [this, self](ErrorCode ec, std::size_t bytesTransferred) { + onRead(ec, bytesTransferred); + }; + + socket_.async_read_some(buffer(buffer_), handler); +} + +void SessionBase::onRead(ErrorCode ec, std::size_t bytesTransferred) +{ + if (ec) { + Logger::error("Read error: " + ec.message()); + return; + } + + std::string rawRequest(buffer_.data(), bytesTransferred); + Logger::info("Received request:\n" + rawRequest); + + try { + handleRequest(rawRequest); + } catch (const std::exception &ex) { + Logger::error(std::string("Request handling failed: ") + ex.what()); + } +} + +void SessionBase::doWrite(const std::string &response) +{ + using namespace boost::asio; + + auto self = shared_from_this(); + auto onWriteHandler = + [this, self](ErrorCode ec, std::size_t /*bytesTransferred*/) { + if (ec) { + Logger::error("Write error: " + ec.message()); + } + + ErrorCode shutdownEc; + if (socket_.shutdown(TcpSocket::shutdown_both, shutdownEc)) { + Logger::warn("Shutdown error: " + shutdownEc.message()); + } + + ErrorCode closeEc; + if (socket_.close(closeEc)) { + Logger::warn("Close error: " + closeEc.message()); + } else { + Logger::info("Connection closed"); + } + }; + + async_write(socket_, buffer(response), onWriteHandler); +} diff --git a/src/common/session_base.hpp b/src/common/session_base.hpp new file mode 100644 index 0000000..f480971 --- /dev/null +++ b/src/common/session_base.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "aliases.hpp" +#include +#include +#include + +class SessionBase : public std::enable_shared_from_this +{ + public: + explicit SessionBase(TcpSocket socket); + virtual ~SessionBase() = default; + + void start(); + + protected: + virtual void handleRequest(const std::string_view rawRequest) = 0; + + void doRead(); + void onRead(ErrorCode ec, std::size_t bytesTransferred); + void doWrite(const std::string &response); + + TcpSocket socket_; + std::array buffer_; +}; diff --git a/src/common/tcp_server_base.cpp b/src/common/tcp_server_base.cpp new file mode 100644 index 0000000..3adce56 --- /dev/null +++ b/src/common/tcp_server_base.cpp @@ -0,0 +1,23 @@ +#include "tcp_server_base.hpp" +#include "logger.hpp" + +TcpServerBase::TcpServerBase(IoContext &io, unsigned short port) + : acceptor_(io, TcpEndpoint(boost::asio::ip::tcp::v4(), port)) +{ +} + +void TcpServerBase::start() +{ + auto doAccept = [this]() { + acceptor_.async_accept([this](ErrorCode ec, TcpSocket socket) { + if (!ec) { + Logger::info("New client connected"); + createSession(std::move(socket)); + } else { + Logger::error("Accept error: " + ec.message()); + } + start(); // continue accepting + }); + }; + doAccept(); +} diff --git a/src/common/tcp_server_base.hpp b/src/common/tcp_server_base.hpp new file mode 100644 index 0000000..62a588e --- /dev/null +++ b/src/common/tcp_server_base.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "aliases.hpp" + +class TcpServerBase +{ + public: + TcpServerBase(IoContext &io, unsigned short port); + virtual ~TcpServerBase() = default; + + void start(); + + protected: + virtual void createSession(TcpSocket socket) = 0; + + TcpAcceptor acceptor_; +}; diff --git a/src/common/udp_server_base.cpp b/src/common/udp_server_base.cpp new file mode 100644 index 0000000..0ce8ea5 --- /dev/null +++ b/src/common/udp_server_base.cpp @@ -0,0 +1,46 @@ +#include "udp_server_base.hpp" +#include "logger.hpp" + +UdpServerBase::UdpServerBase(IoContext &io, unsigned short port) + : socket_(io, UdpEndpoint(boost::asio::ip::udp::v4(), port)) +{ +} + +void UdpServerBase::start() +{ + doReceive(); +} + +void UdpServerBase::doReceive() +{ + using namespace boost::asio; + + auto onReceive = [this](ErrorCode ec, std::size_t bytesReceived) { + if (!ec && bytesReceived > 0) { + std::string_view data(buffer_.data(), bytesReceived); + handleRequest(data, remoteEndpoint_); + } else if (ec) { + Logger::error("UDP receive error: " + ec.message()); + } + doReceive(); // continue listening + }; + + socket_.async_receive_from(buffer(buffer_), remoteEndpoint_, onReceive); +} + +void UdpServerBase::doSend( + const std::string &response, const UdpEndpoint &target +) +{ + using namespace boost::asio; + + auto bufferToSend = std::make_shared(response); + + auto onSend = [bufferToSend](ErrorCode ec, std::size_t /*bytesSent*/) { + if (ec) { + Logger::error("UDP send error: " + ec.message()); + } + }; + + socket_.async_send_to(buffer(*bufferToSend), target, onSend); +} diff --git a/src/common/udp_server_base.hpp b/src/common/udp_server_base.hpp new file mode 100644 index 0000000..956d6a3 --- /dev/null +++ b/src/common/udp_server_base.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "aliases.hpp" + +class UdpServerBase +{ + public: + UdpServerBase(IoContext &io, unsigned short port); + virtual ~UdpServerBase() = default; + + void start(); + + protected: + virtual void + handleRequest(const std::string_view data, const UdpEndpoint &sender) = 0; + + void doReceive(); + void doSend(const std::string &response, const UdpEndpoint &target); + + UdpSocket socket_; + std::array buffer_; + UdpEndpoint remoteEndpoint_; +};