diff --git a/lib/malloy/client/controller.cpp b/lib/malloy/client/controller.cpp index b6d36e6d..b1b11c07 100644 --- a/lib/malloy/client/controller.cpp +++ b/lib/malloy/client/controller.cpp @@ -18,6 +18,17 @@ auto controller::run() -> bool return true; } +auto controller::init(config cfg) -> bool { + if (!malloy::controller::init(cfg)) { + return false; + } + m_cfg = std::move(cfg); + return true; +} +auto controller::start() -> bool { + return root_start(m_cfg); +} + #if MALLOY_FEATURE_TLS bool controller::init_tls() { diff --git a/lib/malloy/client/controller.hpp b/lib/malloy/client/controller.hpp index a713798f..b4ac819b 100644 --- a/lib/malloy/client/controller.hpp +++ b/lib/malloy/client/controller.hpp @@ -8,6 +8,8 @@ #include "../core/http/response.hpp" #include "../core/http/type_traits.hpp" #include "../core/error.hpp" +#include "malloy/core/http/utils.hpp" + #if MALLOY_FEATURE_TLS #include "http/connection_tls.hpp" @@ -66,11 +68,20 @@ namespace malloy::client public: struct config : malloy::controller::config { + + /** + * @brief Agent string used for connections + * @details Set as the User-Agent in http headers + */ + std::string user_agent{"malloy-client"}; }; controller() = default; ~controller() override = default; + [[nodiscard("init may fail")]] + auto init(config cfg) -> bool; + #if MALLOY_FEATURE_TLS /** * Initialize the TLS context. @@ -97,25 +108,7 @@ namespace malloy::client requires concepts::http_callback [[nodiscard]] auto http_request(malloy::http::request req, Callback&& done, Filter filter = {}) -> std::future { - - // Create connection - auto conn = std::make_shared>>( - m_cfg.logger->clone(m_cfg.logger->name() + " | HTTP connection"), - io_ctx() - ); - - // Run - std::promise prom; - auto err_channel = prom.get_future(); - conn->run( - std::to_string(req.port()).c_str(), - req, - std::move(prom), - std::move(done), - std::move(filter) - ); - - return err_channel; + return make_http_connection(std::move(req), std::forward(done), std::move(filter)); } #if MALLOY_FEATURE_TLS @@ -128,27 +121,7 @@ namespace malloy::client requires concepts::http_callback [[nodiscard]] auto https_request(malloy::http::request req, Callback&& done, Filter filter = {}) -> std::future { - check_tls(); - - // Create connection - auto conn = std::make_shared>>( - m_cfg.logger->clone(m_cfg.logger->name() + " | HTTP connection"), - io_ctx(), - *m_tls_ctx - ); - - // Run - std::promise prom; - auto err_channel = prom.get_future(); - conn->run( - std::to_string(req.port()).c_str(), - req, - std::move(prom), - std::move(done), - std::move(filter) - ); - - return err_channel; + return make_http_connection(std::move(req), std::forward(done), std::move(filter)); } /** @@ -219,8 +192,13 @@ namespace malloy::client */ auto run() -> bool; + auto start() -> bool; + + protected: + private: std::shared_ptr m_tls_ctx; + config m_cfg; /** * Checks whether the TLS context was initialized. @@ -233,6 +211,44 @@ namespace malloy::client throw std::logic_error("TLS context not initialized."); } + template + auto make_http_connection(malloy::http::request&& req, Callback&& cb, Filter&& filter) -> std::future + { + + std::promise prom; + auto err_channel = prom.get_future(); + [this](auto&& cb) { +#if MALLOY_FEATURE_TLS + if constexpr (isHttps) { + init_tls(); + cb(std::make_shared>>( + m_cfg.logger->clone(m_cfg.logger->name() + " | HTTP connection"), + io_ctx(), + *m_tls_ctx)); + return; + } +#endif + cb(std::make_shared>>( + m_cfg.logger->clone(m_cfg.logger->name() + " | HTTP connection"), + io_ctx())); + }([this, prom = std::move(prom), req = std::move(req), filter = std::forward(filter), cb = std::forward(cb)](auto&& conn) mutable { + if (!malloy::http::has_field(req, malloy::http::field::user_agent)) { + req.set(malloy::http::field::user_agent, m_cfg.user_agent); + } + + // Run + conn->run( + std::to_string(req.port()).c_str(), + req, + std::move(prom), + std::forward(cb), + std::forward(filter)); + }); + + return err_channel; + + } + template void make_ws_connection( const std::string& host, @@ -258,7 +274,7 @@ namespace malloy::client } else #endif return malloy::websocket::stream{boost::beast::tcp_stream{boost::asio::make_strand(io_ctx())}}; - }()); + }(), m_cfg.user_agent); conn->connect(results, resource, [conn, done = std::forward(done)](auto ec) mutable { if (ec) { diff --git a/lib/malloy/core/controller.cpp b/lib/malloy/core/controller.cpp index fd4e5715..c6d8f5a2 100644 --- a/lib/malloy/core/controller.cpp +++ b/lib/malloy/core/controller.cpp @@ -11,7 +11,7 @@ controller::~controller() stop().wait(); } -bool controller::init(config cfg) +bool controller::init(const config& cfg) { // Don't initialize if not stopped if (m_state != state::stopped) @@ -32,8 +32,6 @@ bool controller::init(config cfg) return false; } - // Grab the config - m_cfg = std::move(cfg); // Create the I/O context m_io_ctx = std::make_shared(); @@ -45,17 +43,17 @@ bool controller::init(config cfg) return true; } -bool controller::start() +bool controller::root_start(const config& cfg) { // Sanity check if (!m_io_ctx) { - m_cfg.logger->critical("no I/O context present. Make sure that init() was called and succeeded."); + cfg.logger->critical("no I/O context present. Make sure that init() was called and succeeded."); return false; } // Create the I/O context threads - m_io_threads.reserve(m_cfg.num_threads - 1); - for (std::size_t i = 0; i < m_cfg.num_threads; i++) { + m_io_threads.reserve(cfg.num_threads - 1); + for (std::size_t i = 0; i < cfg.num_threads; i++) { m_io_threads.emplace_back( [this] { @@ -65,7 +63,7 @@ bool controller::start() } // Log - m_cfg.logger->debug("starting i/o context."); + cfg.logger->debug("starting i/o context."); // Update state m_state = state::running; @@ -97,14 +95,12 @@ std::future controller::stop() // Tell the workguard that we no longer need it's service m_workguard->reset(); - m_cfg.logger->debug("waiting for I/O threads to stop..."); for (auto& thread : m_io_threads) thread.join(); m_state = state::stopped; - m_cfg.logger->debug("all I/O threads stopped."); } ); } diff --git a/lib/malloy/core/controller.hpp b/lib/malloy/core/controller.hpp index 649f47a2..78dbefe7 100644 --- a/lib/malloy/core/controller.hpp +++ b/lib/malloy/core/controller.hpp @@ -41,18 +41,7 @@ namespace malloy controller() = default; virtual ~controller(); - [[nodiscard("init may fail")]] - virtual - bool init(config cfg); - /** - * Start the server. This function will not return until the server is stopped. - * - * @return Whether starting the server was successful. - */ - [[nodiscard("start may fail")]] - virtual - bool start(); /** * Stop the server. @@ -64,7 +53,8 @@ namespace malloy std::future stop(); protected: - config m_cfg; + [[nodiscard("init may fail")]] + bool init(const config& cfg); [[nodiscard]] boost::asio::io_context& @@ -72,6 +62,9 @@ namespace malloy { return *m_io_ctx; } + [[nodiscard("start may fail")]] + auto root_start(const config& cfg) -> bool; + void remove_workguard() const; diff --git a/lib/malloy/core/http/utils.hpp b/lib/malloy/core/http/utils.hpp index dbb9a893..ee85bdb6 100644 --- a/lib/malloy/core/http/utils.hpp +++ b/lib/malloy/core/http/utils.hpp @@ -22,6 +22,12 @@ namespace malloy::http { head.target(head.target().substr(resource.size())); } + + template + auto has_field(const boost::beast::http::header& head, const malloy::http::field check) -> bool + { + return head.find(check) != head.end(); + } } diff --git a/lib/malloy/core/websocket/connection.hpp b/lib/malloy/core/websocket/connection.hpp index 317a8bec..5961fe28 100644 --- a/lib/malloy/core/websocket/connection.hpp +++ b/lib/malloy/core/websocket/connection.hpp @@ -18,20 +18,6 @@ namespace malloy::websocket { - namespace detail - { - constexpr std::string_view beast_version = BOOST_BEAST_VERSION_STRING; - template - constexpr auto ws_agent_string() -> std::string_view - { - if (isClient) { - return {BOOST_BEAST_VERSION_STRING " websocket-client-async"}; - } else { - return {BOOST_BEAST_VERSION_STRING " malloy"}; - } - } - } // namespace detail - /** * @class connection * @tparam isClient: Whether it is the client end of a websocket connection @@ -54,11 +40,6 @@ namespace malloy::websocket closed }; - /** - * The agent string. - */ - constexpr static std::string_view agent_string = detail::ws_agent_string(); - /** * See stream::set_binary(bool) */ @@ -76,12 +57,12 @@ namespace malloy::websocket * `connect` must be called before this connection can be used */ static auto - make(const std::shared_ptr logger, stream&& ws) -> std::shared_ptr + make(const std::shared_ptr logger, stream&& ws, const std::string& agent_string) -> std::shared_ptr { // We have to emulate make_shared here because the ctor is private connection* me = nullptr; try { - me = new connection{logger, std::move(ws)}; + me = new connection{logger, std::move(ws), agent_string}; return std::shared_ptr{me}; } catch (...) { delete me; @@ -254,13 +235,15 @@ namespace malloy::websocket std::vector> msg_queue_; std::shared_ptr m_logger; stream m_ws; + std::string m_agent_string; enum state m_state = state::closed; connection( - std::shared_ptr logger, stream&& ws) : + std::shared_ptr logger, stream&& ws, std::string agent_str) : m_logger(std::move(logger)), - m_ws{std::move(ws)} + m_ws{std::move(ws)}, + m_agent_string{std::move(agent_str)} { // Sanity check logger if (!m_logger) @@ -275,21 +258,12 @@ namespace malloy::websocket boost::beast::websocket::stream_base::timeout::suggested( isClient ? boost::beast::role_type::client : boost::beast::role_type::server)); - if constexpr (isClient) { - // Set a decorator to change the User-Agent of the handshake - m_ws.set_option( - boost::beast::websocket::stream_base::decorator( - [](boost::beast::websocket::request_type& req) { - req.set(boost::beast::http::field::user_agent, agent_string); - })); - } else { - // Set a decorator to change the Server of the handshake - m_ws.set_option( - boost::beast::websocket::stream_base::decorator( - [](boost::beast::websocket::response_type& res) { - res.set(boost::beast::http::field::server, agent_string); - })); - } + const auto agent_field = isClient ? malloy::http::field::user_agent : malloy::http::field::server; + m_ws.set_option( + boost::beast::websocket::stream_base::decorator( + [this, agent_field](boost::beast::websocket::request_type& req) { + req.set(agent_field, m_agent_string); + })); } void diff --git a/lib/malloy/server/controller.cpp b/lib/malloy/server/controller.cpp index 2ee00bf2..cd007bfb 100644 --- a/lib/malloy/server/controller.cpp +++ b/lib/malloy/server/controller.cpp @@ -11,18 +11,15 @@ using namespace malloy::server; -bool controller::init(config cfg) -{ - // Base class - if (!malloy::controller::init(cfg)) +auto controller::init(config cfg) -> bool { + if (!malloy::controller::init(cfg)) { return false; - + } // Grab the config m_cfg = std::move(cfg); // Create the top-level router - m_router = std::make_shared(m_cfg.logger->clone("router")); - + m_router = std::make_shared(m_cfg.logger->clone("router"), m_cfg.agent_string); return true; } @@ -68,14 +65,14 @@ bool controller::start() m_tls_ctx, boost::asio::ip::tcp::endpoint{ boost::asio::ip::make_address(m_cfg.interface), m_cfg.port }, m_router, - std::make_shared(m_cfg.doc_root) - ); + std::make_shared(m_cfg.doc_root), + m_cfg.agent_string); // Run the listener m_listener->run(); // Base class - if (!malloy::controller::start()) + if (!root_start(m_cfg)) return false; return true; diff --git a/lib/malloy/server/controller.hpp b/lib/malloy/server/controller.hpp index 6bfda9a9..0f6ce3a2 100644 --- a/lib/malloy/server/controller.hpp +++ b/lib/malloy/server/controller.hpp @@ -2,6 +2,7 @@ #include "../core/controller.hpp" + #include #include #include @@ -50,6 +51,12 @@ namespace malloy::server * to the working directory. */ std::filesystem::path doc_root = "."; + + /** + * @brief Agent string used for connections + * @details Set as the Server field in http headers + */ + std::string agent_string{"malloy-server"}; }; controller() = default; @@ -68,6 +75,7 @@ namespace malloy::server * @param cfg The configuration to use. * @return Whether the initialization was successful. */ + [[nodiscard("init may fail")]] bool init(config cfg); /** @@ -75,7 +83,7 @@ namespace malloy::server * * @return Whether starting the server was successful. */ - bool start() override; + bool start(); #if MALLOY_FEATURE_TLS /** diff --git a/lib/malloy/server/http/connection.hpp b/lib/malloy/server/http/connection.hpp index 9fcb4bac..ca714238 100644 --- a/lib/malloy/server/http/connection.hpp +++ b/lib/malloy/server/http/connection.hpp @@ -107,6 +107,7 @@ namespace malloy::server::http struct config { std::uint64_t request_body_limit = 10 * 10e6; ///< The maximum allowed body request size in bytes. + std::string agent_string; ///< Agent string to use, set by the controller }; /** @@ -249,7 +250,7 @@ namespace malloy::server::http // of both the socket and the HTTP request. auto ws_connection = server::websocket::connection::make( m_logger->clone("websocket_connection"), - malloy::websocket::stream{derived().release_stream()}); + malloy::websocket::stream{derived().release_stream()}, cfg.agent_string); m_router->websocket(*m_doc_root, gen, ws_connection); diff --git a/lib/malloy/server/http/connection_detector.cpp b/lib/malloy/server/http/connection_detector.cpp index d7925972..f169b4a3 100644 --- a/lib/malloy/server/http/connection_detector.cpp +++ b/lib/malloy/server/http/connection_detector.cpp @@ -13,13 +13,15 @@ connection_detector::connection_detector( boost::asio::ip::tcp::socket&& socket, std::shared_ptr ctx, std::shared_ptr doc_root, - std::shared_ptr router + std::shared_ptr router, + std::string agent_string ) : m_logger(std::move(logger)), m_stream(std::move(socket)), m_ctx(std::move(ctx)), m_doc_root(std::move(doc_root)), - m_router(std::move(router)) + m_router(std::move(router)), + m_agent_string{std::move(agent_string)} { // Sanity check logger if (!m_logger) @@ -77,28 +79,28 @@ void connection_detector::on_detect(boost::beast::error_code ec, bool result) // ToDo: Check whether it's okay to fall back to a plain session if a handshake was detected // Currently we'd do this if no TLS context was provided. - #if MALLOY_FEATURE_TLS + [&, this](auto&& cb) { +#if MALLOY_FEATURE_TLS if (result && m_ctx) { // Launch TLS connection - std::make_shared( + cb(std::make_shared( m_logger, m_stream.release_socket(), m_ctx, std::move(m_buffer), m_doc_root, - std::make_shared>(m_router) - )->run(); - - return; + std::make_shared>(m_router))); } - #endif +#endif - // Launch plain connection - std::make_shared( - m_logger, - m_stream.release_socket(), - std::move(m_buffer), - m_doc_root, - std::make_shared>(m_router) - )->run(); + cb(std::make_shared( + m_logger, + m_stream.release_socket(), + std::move(m_buffer), + m_doc_root, + std::make_shared>(m_router))); + }([this](auto&& conn) { + conn->cfg.agent_string = m_agent_string; + conn->run(); + }); } diff --git a/lib/malloy/server/http/connection_detector.hpp b/lib/malloy/server/http/connection_detector.hpp index ebc3a7aa..2f2e03d7 100644 --- a/lib/malloy/server/http/connection_detector.hpp +++ b/lib/malloy/server/http/connection_detector.hpp @@ -44,7 +44,8 @@ namespace malloy::server::http boost::asio::ip::tcp::socket&& socket, std::shared_ptr ctx, std::shared_ptr doc_root, - std::shared_ptr router + std::shared_ptr router, + std::string agent_string ); /** @@ -59,6 +60,7 @@ namespace malloy::server::http boost::beast::flat_buffer m_buffer; std::shared_ptr m_doc_root; std::shared_ptr m_router; + std::string m_agent_string; void on_detect(boost::beast::error_code ec, bool result); }; diff --git a/lib/malloy/server/listener.cpp b/lib/malloy/server/listener.cpp index c886e70a..c543699e 100644 --- a/lib/malloy/server/listener.cpp +++ b/lib/malloy/server/listener.cpp @@ -13,14 +13,16 @@ listener::listener( std::shared_ptr tls_ctx, const boost::asio::ip::tcp::endpoint& endpoint, std::shared_ptr router, - std::shared_ptr http_doc_root + std::shared_ptr http_doc_root, + std::string agent_string ) : m_logger(std::move(logger)), m_io_ctx(ioc), m_tls_ctx(std::move(tls_ctx)), m_acceptor(boost::asio::make_strand(ioc)), m_router(std::move(router)), - m_doc_root(std::move(http_doc_root)) + m_doc_root(std::move(http_doc_root)), + m_agent_string{std::move(agent_string)} { boost::beast::error_code ec; @@ -113,7 +115,8 @@ void listener::on_accept(boost::beast::error_code ec, boost::asio::ip::tcp::sock std::move(socket), m_tls_ctx, m_doc_root, - m_router + m_router, + m_agent_string ); // Run the HTTP connection diff --git a/lib/malloy/server/listener.hpp b/lib/malloy/server/listener.hpp index ef69ff5d..eea0afc7 100644 --- a/lib/malloy/server/listener.hpp +++ b/lib/malloy/server/listener.hpp @@ -49,7 +49,8 @@ namespace malloy::server std::shared_ptr tls_ctx, const boost::asio::ip::tcp::endpoint& endpoint, std::shared_ptr router, - std::shared_ptr http_doc_root + std::shared_ptr http_doc_root, + std::string agent_string ); /** @@ -106,6 +107,7 @@ namespace malloy::server boost::asio::ip::tcp::acceptor m_acceptor; std::shared_ptr m_router; std::shared_ptr m_doc_root; + std::string m_agent_string; /** * Start accepting incoming requests. diff --git a/lib/malloy/server/routing/router.cpp b/lib/malloy/server/routing/router.cpp index c9ed3fc8..92221b58 100644 --- a/lib/malloy/server/routing/router.cpp +++ b/lib/malloy/server/routing/router.cpp @@ -7,8 +7,8 @@ using namespace malloy::server; -router::router(std::shared_ptr logger) : - m_logger(std::move(logger)) +router::router(std::shared_ptr logger, std::string server_str) : + m_logger(std::move(logger)), m_server_str{std::move(server_str)} { } @@ -131,9 +131,7 @@ bool router::add_file_serving(std::string resource, std::filesystem::path storag ep->resource_base = resource; ep->base_path = std::move(storage_base_path); - ep->writer = [](const auto& req, auto&& resp, const auto& conn) { - std::visit([&](auto&& resp) { detail::send_response(req, std::move(resp), conn); }, std::move(resp)); - }; + ep->writer = make_endpt_writer_callback(); // Add return add_http_endpoint(std::move(ep)); diff --git a/lib/malloy/server/routing/router.hpp b/lib/malloy/server/routing/router.hpp index f470de24..b17e6862 100644 --- a/lib/malloy/server/routing/router.hpp +++ b/lib/malloy/server/routing/router.hpp @@ -73,12 +73,14 @@ namespace malloy::server * @param connection The connection. */ template - void send_response(const boost::beast::http::request_header<>& req, malloy::http::response&& resp, http::connection_t connection) + void send_response(const boost::beast::http::request_header<>& req, malloy::http::response&& resp, http::connection_t connection, std::string_view server_str) { // Add more information to the response //resp.keep_alive(req.keep_alive); // TODO: Is this needed?, if so its a spanner in the works resp.version(req.version()); - resp.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); + if (!malloy::http::has_field(resp, malloy::http::field::server)) { + resp.set(boost::beast::http::field::server, server_str); + } resp.prepare_payload(); std::visit([resp = std::move(resp)](auto& c) mutable { @@ -127,7 +129,7 @@ namespace malloy::server * * @param logger The logger instance to use. */ - explicit router(std::shared_ptr logger); + router(std::shared_ptr logger, std::string server_str); /** * Copy constructor. @@ -329,7 +331,7 @@ namespace malloy::server auto resp = ep->handle(req, connection); if (resp) { // Send the response - detail::send_response(req->header(), std::move(*resp), connection); + detail::send_response(req->header(), std::move(*resp), connection, m_server_str); } // We're done handling this request @@ -337,7 +339,7 @@ namespace malloy::server } // If we end up where we have no meaningful way of handling this request - detail::send_response(req->header(), malloy::http::generator::bad_request("unknown request"), connection); + detail::send_response(req->header(), malloy::http::generator::bad_request("unknown request"), connection, m_server_str); } /** @@ -385,6 +387,14 @@ namespace malloy::server std::unordered_map> m_sub_routers; std::vector> m_endpoints_http; std::vector> m_endpoints_websocket; + std::string m_server_str{BOOST_BEAST_VERSION_STRING}; + + /// Create the lambda wrapped callback for the writer + auto make_endpt_writer_callback() { + return [this](const auto& req, R&& resp, const auto& conn) { + std::visit([&, this](Re&& resp) { detail::send_response(req, std::forward(resp), conn, m_server_str); }, std::forward(resp)); + }; + } template< bool UsesCaptures, @@ -432,9 +442,7 @@ namespace malloy::server return false; } - ep->writer = [this](const auto& req, auto&& resp, const auto& conn) { - std::visit([&, this](auto&& resp) { detail::send_response(req, std::move(resp), conn); }, std::move(resp)); - }; + ep->writer = make_endpt_writer_callback(); // Add route return add_http_endpoint(std::move(ep)); diff --git a/test/test_suites/components/CMakeLists.txt b/test/test_suites/components/CMakeLists.txt index 66c2d1c2..3ae224a3 100644 --- a/test/test_suites/components/CMakeLists.txt +++ b/test/test_suites/components/CMakeLists.txt @@ -8,4 +8,5 @@ target_sources( request.cpp websockets.cpp stream.cpp + controller.cpp ) diff --git a/test/test_suites/components/controller.cpp b/test/test_suites/components/controller.cpp new file mode 100644 index 00000000..5dfa3ccb --- /dev/null +++ b/test/test_suites/components/controller.cpp @@ -0,0 +1,59 @@ +#include "../../test.hpp" + +#include +#include +#include + +namespace mc = malloy::client; +namespace ms = malloy::server; + +TEST_SUITE("controller - roundtrips") { + TEST_CASE("Server and client set agent strings based on user_agent") { + constexpr auto cli_agent_str = "test-cli"; + constexpr auto serve_agent_str = "test-serve"; + constexpr auto addr = "127.0.0.1"; + constexpr uint16_t port = 55123; + + + malloy::controller::config general_cfg; + general_cfg.logger = spdlog::default_logger(); + + mc::controller::config cli_cfg{general_cfg}; + ms::controller::config serve_cfg{general_cfg}; + + cli_cfg.user_agent = cli_agent_str; + serve_cfg.agent_string = serve_agent_str; + serve_cfg.interface = addr; + serve_cfg.port = port; + + mc::controller cli_ctrl; + + REQUIRE(cli_ctrl.init(cli_cfg)); + + malloy::http::request<> req{ + malloy::http::method::get, + addr, + port, + "/" + }; + auto stop_tkn = cli_ctrl.http_request(req, [&](auto&& resp){ + CHECK(resp[malloy::http::field::server] == serve_agent_str); + }); + + ms::controller serve_ctrl; + + REQUIRE(serve_ctrl.init(serve_cfg)); + + serve_ctrl.router()->add(malloy::http::method::get, "/", [&](auto&& req){ + CHECK(req[malloy::http::field::user_agent] == cli_agent_str); + return malloy::http::generator::ok(); + }); + + REQUIRE(serve_ctrl.start()); + REQUIRE(cli_ctrl.run()); + + CHECK(!stop_tkn.get()); + + } +} + diff --git a/test/test_suites/components/websockets.cpp b/test/test_suites/components/websockets.cpp index 4296a08e..01f182c0 100644 --- a/test/test_suites/components/websockets.cpp +++ b/test/test_suites/components/websockets.cpp @@ -89,8 +89,10 @@ namespace server_cfg.interface = "127.0.0.1"; server_cfg.port = port; + mc::controller::config cli_cfg{general_cfg}; + REQUIRE(s_ctrl.init(server_cfg)); - REQUIRE(c_ctrl.init(general_cfg)); + REQUIRE(c_ctrl.init(cli_cfg)); setup_server(s_ctrl); setup_client(c_ctrl);