diff --git a/broker/CMakeLists.txt b/broker/CMakeLists.txt index f1d1e076fc6..af3a9ac809c 100644 --- a/broker/CMakeLists.txt +++ b/broker/CMakeLists.txt @@ -521,7 +521,6 @@ add_broker_module(TLS2 OFF) add_broker_module(DUMP OFF) add_broker_module(GRPC ON) add_broker_module(VICTORIA_METRICS ON) -add_subdirectory(http_client) add_subdirectory(http_tsdb) # Lua module. diff --git a/broker/http_client/precomp_inc/precomp.hh b/broker/http_client/precomp_inc/precomp.hh deleted file mode 100644 index 07c1e1cc6d0..00000000000 --- a/broker/http_client/precomp_inc/precomp.hh +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 Centreon (https://www.centreon.com/) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - * - */ - -#ifndef CCB_HTTP_CLIENT_PRECOMP_HH -#define CCB_HTTP_CLIENT_PRECOMP_HH - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include "com/centreon/exceptions/msg_fmt.hh" - -using system_clock = std::chrono::system_clock; -using time_point = system_clock::time_point; -using duration = system_clock::duration; - -namespace asio = boost::asio; - -#endif // CCB_HTTP_CLIENT_PRECOMP_HH diff --git a/broker/http_tsdb/CMakeLists.txt b/broker/http_tsdb/CMakeLists.txt index ba26cfdaaa8..0303f80e592 100644 --- a/broker/http_tsdb/CMakeLists.txt +++ b/broker/http_tsdb/CMakeLists.txt @@ -19,10 +19,10 @@ # Global options. set(INC_DIR "${PROJECT_SOURCE_DIR}/http_tsdb/inc/com/centreon/broker/http_tsdb") -set(HTTP_CLIENT_INC_DIR "${PROJECT_SOURCE_DIR}/http_client/inc") +set(HTTP_INC_DIR "${CMAKE_SOURCE_DIR}/common/http/inc") set(SRC_DIR "src") set(TEST_DIR "${PROJECT_SOURCE_DIR}/http_tsdb/test") -include_directories(inc ${INC_DIR} ${HTTP_CLIENT_INC_DIR}) +include_directories(inc ${INC_DIR} ${HTTP_INC_DIR}) # Sources. set(SOURCES diff --git a/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/http_tsdb_config.hh b/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/http_tsdb_config.hh index 28345d663c4..069e2d32655 100644 --- a/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/http_tsdb_config.hh +++ b/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/http_tsdb_config.hh @@ -1,31 +1,32 @@ -/* -** Copyright 2022 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #ifndef CCB_HTTP_TSDB_CONFIG_HH #define CCB_HTTP_TSDB_CONFIG_HH #include "column.hh" -#include "com/centreon/broker/http_client/http_config.hh" +#include "com/centreon/common/http/http_config.hh" namespace com::centreon::broker { namespace http_tsdb { -class http_tsdb_config : public http_client::http_config { +class http_tsdb_config : public common::http::http_config { std::string _http_target; std::string _user; std::string _pwd; @@ -34,14 +35,14 @@ class http_tsdb_config : public http_client::http_config { std::vector _metric_columns; public: - http_tsdb_config(const http_client::http_config& http_conf, + http_tsdb_config(const common::http::http_config& http_conf, const std::string& http_target, const std::string& user, const std::string& pwd, unsigned max_queries_per_transaction, const std::vector& status_columns, const std::vector& metric_columns) - : http_client::http_config(http_conf), + : common::http::http_config(http_conf), _http_target(http_target), _user(user), _pwd(pwd), @@ -50,9 +51,9 @@ class http_tsdb_config : public http_client::http_config { _metric_columns(metric_columns) {} http_tsdb_config() : _max_queries_per_transaction(0) {} - http_tsdb_config(const http_client::http_config& http_conf, + http_tsdb_config(const common::http::http_config& http_conf, unsigned max_queries_per_transaction) - : http_client::http_config(http_conf), + : common::http::http_config(http_conf), _max_queries_per_transaction(max_queries_per_transaction) {} const std::string& get_http_target() const { return _http_target; } @@ -71,6 +72,6 @@ class http_tsdb_config : public http_client::http_config { }; } // namespace http_tsdb -} +} // namespace com::centreon::broker #endif diff --git a/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/stream.hh b/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/stream.hh index a65c73838b2..bbe341c1120 100644 --- a/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/stream.hh +++ b/broker/http_tsdb/inc/com/centreon/broker/http_tsdb/stream.hh @@ -1,38 +1,39 @@ -/* -** Copyright 2011-2017 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #ifndef CCB_HTTP_TSDB_STREAM_HH #define CCB_HTTP_TSDB_STREAM_HH -#include "com/centreon/broker/http_client/http_client.hh" #include "com/centreon/broker/io/stream.hh" #include "com/centreon/broker/persistent_cache.hh" +#include "com/centreon/common/http/http_client.hh" #include "http_tsdb_config.hh" #include "internal.hh" #include "line_protocol_query.hh" -namespace http_client = com::centreon::broker::http_client; +namespace http = com::centreon::common::http; namespace com::centreon::broker { namespace http_tsdb { -class request : public http_client::request_base { +class request : public http::request_base { protected: unsigned _nb_metric; unsigned _nb_status; @@ -43,9 +44,8 @@ class request : public http_client::request_base { request() : _nb_metric(0), _nb_status(0) {} request(boost::beast::http::verb method, const std::string& server_name, - boost::beast::string_view target, - unsigned version = 11) - : http_client::request_base(method, server_name, target), + boost::beast::string_view target) + : http::request_base(method, server_name, target), _nb_metric(0), _nb_status(0) {} @@ -67,7 +67,7 @@ inline std::ostream& operator<<(std::ostream& str, const request& req) { return str; } -/** +/* * @class stream stream.hh "com/centreon/broker/influxdb/stream.hh" * @brief tsdb stream. * This class is a base class @@ -82,7 +82,7 @@ class stream : public io::stream, public std::enable_shared_from_this { // Database and http parameters std::shared_ptr _conf; - http_client::client::pointer _http_client; + http::client::pointer _http_client; // number of metric and status sent to tsdb and acknowledged by a 20x response unsigned _acknowledged; @@ -90,7 +90,7 @@ class stream : public io::stream, public std::enable_shared_from_this { request::pointer _request; // the two beans stat_unit and stat_average are used to produce statistics // about request time - /** + /* * @brief stat cumul * this bean is used to cumulate request for example during one second */ @@ -106,7 +106,7 @@ class stream : public io::stream, public std::enable_shared_from_this { stat _metric_stat; stat _status_stat; - /** + /* * @brief this cless calc an average over a period * */ @@ -129,8 +129,7 @@ class stream : public io::stream, public std::enable_shared_from_this { const std::shared_ptr& io_context, const std::shared_ptr& logger, const std::shared_ptr& conf, - http_client::client::connection_creator conn_creator = - http_client::http_connection::load); + http::connection_creator conn_creator); virtual request::pointer create_request() const = 0; @@ -139,7 +138,7 @@ class stream : public io::stream, public std::enable_shared_from_this { void send_handler(const boost::beast::error_code& err, const std::string& detail, const request::pointer& request, - const http_client::response_ptr& response); + const http::response_ptr& response); void add_to_stat(stat& to_maj, unsigned to_add); @@ -155,6 +154,6 @@ class stream : public io::stream, public std::enable_shared_from_this { }; } // namespace http_tsdb -} +} // namespace com::centreon::broker #endif // !CCB_HTTP_TSDB_STREAM_HH diff --git a/broker/http_tsdb/src/factory.cc b/broker/http_tsdb/src/factory.cc index 8a790ae5249..d456c708776 100644 --- a/broker/http_tsdb/src/factory.cc +++ b/broker/http_tsdb/src/factory.cc @@ -1,20 +1,21 @@ /** -* Copyright 2011-2017 Centreon -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* For more information : contact@centreon.com -*/ + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #include "com/centreon/broker/http_tsdb/factory.hh" @@ -244,7 +245,7 @@ void factory::create_conf(const config::endpoint& cfg, certificate_path = it->second; } - http_client::http_config http_cfg( + common::http::http_config http_cfg( res_it->endpoint(), addr, encryption, connect_timeout, send_timeout, receive_timeout, second_tcp_keep_alive_interval, std::chrono::seconds(1), 0, default_http_keepalive_duration, max_connections, ssl_method, diff --git a/broker/http_tsdb/src/stream.cc b/broker/http_tsdb/src/stream.cc index 5deb405ec6f..dc78b102350 100644 --- a/broker/http_tsdb/src/stream.cc +++ b/broker/http_tsdb/src/stream.cc @@ -1,20 +1,21 @@ /** -* Copyright 2022 Centreon -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* For more information : contact@centreon.com -*/ + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #include "com/centreon/broker/http_tsdb/stream.hh" #include "bbdo/storage/metric.hh" @@ -105,7 +106,7 @@ unsigned stream::stat_average::get_average() const { /** * @brief Construct a new stream::stream object - * conn_creator is used by this object to construct http_client::connection_base + * conn_creator is used by this object to construct http::connection_base * objects conn_creator can construct an http_connection, https_connection or a * mock * @param name @@ -119,7 +120,7 @@ stream::stream(const std::string& name, const std::shared_ptr& io_context, const std::shared_ptr& logger, const std::shared_ptr& conf, - http_client::client::connection_creator conn_creator) + http::connection_creator conn_creator) : io::stream(name), _io_context(io_context), _logger(logger), @@ -130,7 +131,7 @@ stream::stream(const std::string& name, _metric_stat{{0, 0}, {0, 0}}, _status_stat{{0, 0}, {0, 0}} { _http_client = - http_client::client::load(io_context, logger, conf, conn_creator); + common::http::client::load(io_context, logger, conf, conn_creator); } stream::~stream() {} @@ -324,7 +325,7 @@ void stream::send_request(const request::pointer& request) { _http_client->send(request, [me = shared_from_this(), request]( const boost::beast::error_code& err, const std::string& detail, - const http_client::response_ptr& response) { + const common::http::response_ptr& response) { me->send_handler(err, detail, request, response); }); } @@ -344,7 +345,7 @@ static time_point _epoch = system_clock::from_time_t(0); void stream::send_handler(const boost::beast::error_code& err, const std::string& detail, const request::pointer& request, - const http_client::response_ptr& response) { + const common::http::response_ptr& response) { auto actu_stat_avg = [&]() -> void { if (request->get_connect_time() > _epoch && request->get_send_time() > _epoch) { diff --git a/broker/http_tsdb/test/stream_test.cc b/broker/http_tsdb/test/stream_test.cc index 808650a10d1..c5cee36daaf 100644 --- a/broker/http_tsdb/test/stream_test.cc +++ b/broker/http_tsdb/test/stream_test.cc @@ -1,5 +1,5 @@ /** - * Copyright 2022 Centreon (https://www.centreon.com/) + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ using duration = system_clock::duration; using namespace com::centreon::exceptions; using namespace com::centreon::broker; +using namespace com::centreon::common; using namespace nlohmann; extern std::shared_ptr g_io_context; @@ -81,8 +82,7 @@ std::atomic_uint request_test::id_gen(0); class stream_test : public http_tsdb::stream { public: stream_test(const std::shared_ptr& conf, - http_client::client::connection_creator conn_creator = - http_client::http_connection::load) + http::connection_creator conn_creator) : http_tsdb::stream("stream_test", g_io_context, log_v2::tcp(), @@ -94,31 +94,37 @@ class stream_test : public http_tsdb::stream { }; TEST_F(http_tsdb_stream_test, NotRead) { - stream_test test(std::make_shared()); + auto conf = std::make_shared(); + http::connection_creator conn_creator = [conf]() { + return http::http_connection::load(g_io_context, log_v2::tcp(), conf); + }; + stream_test test(conf, conn_creator); std::shared_ptr d(std::make_shared(1)); ASSERT_THROW(test.read(d, 0), msg_fmt); } -class connection_send_bagot : public http_client::connection_base { +class connection_send_bagot : public http::connection_base { + asio::ip::tcp::socket _not_used; + public: static std::atomic_uint success; static std::condition_variable success_cond; connection_send_bagot(const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_client::http_config::pointer& conf) - : connection_base(io_context, logger, conf) {} + const http::http_config::pointer& conf) + : connection_base(io_context, logger, conf), _not_used(*io_context) {} void shutdown() override { _state = e_not_connected; } - void connect(http_client::connect_callback_type&& callback) override { + void connect(http::connect_callback_type&& callback) override { _state = e_idle; _io_context->post([cb = std::move(callback)]() { cb({}, {}); }); } - void send(http_client::request_ptr request, - http_client::send_callback_type&& callback) override { + void send(http::request_ptr request, + http::send_callback_type&& callback) override { if (_state != e_idle) { _io_context->post([cb = std::move(callback)]() { cb(std::make_error_code(std::errc::invalid_argument), "bad state", {}); @@ -147,32 +153,39 @@ class connection_send_bagot : public http_client::connection_base { std::static_pointer_cast(request)->get_request_id(), std::static_pointer_cast(request)->get_nb_data()); _io_context->post([cb = std::move(callback)]() { - auto resp = std::make_shared(); + auto resp = std::make_shared(); resp->keep_alive(false); cb({}, "", resp); }); } } } + + void on_accept(http::connect_callback_type&& callback) override{}; + + void answer(const http::response_ptr& response, + http::answer_callback_type&& callback) override {} + void receive_request(http::request_callback_type&& callback) override {} + + asio::ip::tcp::socket& get_socket() { return _not_used; } }; std::atomic_uint connection_send_bagot::success(0); std::condition_variable connection_send_bagot::success_cond; TEST_F(http_tsdb_stream_test, all_event_sent) { - http_client::http_config conf( + http::http_config conf( asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), 80), "localhost", false, std::chrono::seconds(10), std::chrono::seconds(10), std::chrono::seconds(10), 30, std::chrono::seconds(1), 100, std::chrono::seconds(1), 5); - std::shared_ptr str(std::make_shared( - std::make_shared(conf, 10), - [](const std::shared_ptr& io_context, - const std::shared_ptr& logger, - const http_client::http_config::pointer& conf) { - auto dummy_conn = - std::make_shared(io_context, logger, conf); + auto tsdb_conf = std::make_shared(conf, 10); + + std::shared_ptr str( + std::make_shared(tsdb_conf, [tsdb_conf]() { + auto dummy_conn = std::make_shared( + g_io_context, log_v2::tcp(), tsdb_conf); return dummy_conn; })); diff --git a/broker/test/CMakeLists.txt b/broker/test/CMakeLists.txt index 9469ce778a3..7e7dd8da3b2 100644 --- a/broker/test/CMakeLists.txt +++ b/broker/test/CMakeLists.txt @@ -43,7 +43,7 @@ include_directories(${PROJECT_SOURCE_DIR}/tls/inc) include_directories(${PROJECT_SOURCE_DIR}/tls2/inc) include_directories(${PROJECT_SOURCE_DIR}/grpc/inc) include_directories(${PROJECT_SOURCE_DIR}/grpc/src) -include_directories(${PROJECT_SOURCE_DIR}/http_client/inc) +include_directories(${CMAKE_SOURCE_DIR}/common/http/inc) include_directories(${PROJECT_SOURCE_DIR}/http_tsdb/inc) include_directories(${PROJECT_SOURCE_DIR}/victoria_metrics/inc) include_directories(${PROJECT_SOURCE_DIR}/test/test_util/inc) diff --git a/broker/victoria_metrics/CMakeLists.txt b/broker/victoria_metrics/CMakeLists.txt index c6aec580555..d208dba2c2e 100644 --- a/broker/victoria_metrics/CMakeLists.txt +++ b/broker/victoria_metrics/CMakeLists.txt @@ -18,11 +18,12 @@ # Global options. set(INC_DIR "${PROJECT_SOURCE_DIR}/victoria_metrics/inc/com/centreon/broker/victoria_metrics") +set(HTTP_INC_DIR "${CMAKE_SOURCE_DIR}/common/http/inc") set(SRC_DIR "${PROJECT_SOURCE_DIR}/victoria_metrics/src") set(TEST_DIR "${PROJECT_SOURCE_DIR}/victoria_metrics/test") include_directories(inc ${PROJECT_SOURCE_DIR}/http_tsdb/inc - ${PROJECT_SOURCE_DIR}/http_client/inc + ${HTTP_INC_DIR} ${PROJECT_SOURCE_DIR}/neb/inc ) @@ -53,7 +54,7 @@ add_library("${VICTORIA_METRICS}" SHARED set_target_properties("${VICTORIA_METRICS}" PROPERTIES PREFIX "") target_link_libraries("${VICTORIA_METRICS}" http_tsdb - http_client + centreon_http bbdo_storage pb_storage_lib spdlog::spdlog) target_precompile_headers(${VICTORIA_METRICS} PRIVATE precomp_inc/precomp.hh) diff --git a/broker/victoria_metrics/inc/com/centreon/broker/victoria_metrics/stream.hh b/broker/victoria_metrics/inc/com/centreon/broker/victoria_metrics/stream.hh index 08e44ac54c1..e6c1b338310 100644 --- a/broker/victoria_metrics/inc/com/centreon/broker/victoria_metrics/stream.hh +++ b/broker/victoria_metrics/inc/com/centreon/broker/victoria_metrics/stream.hh @@ -1,20 +1,21 @@ -/* -** Copyright 2022 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #ifndef CCB_VICTORIA_METRICS_STREAM_HH #define CCB_VICTORIA_METRICS_STREAM_HH @@ -42,8 +43,7 @@ class stream : public http_tsdb::stream { stream(const std::shared_ptr& io_context, const std::shared_ptr& conf, const std::string& account_id, - http_client::client::connection_creator conn_creator = - http_client::http_connection::load); + http::connection_creator conn_creator); http_tsdb::request::pointer create_request() const override; @@ -54,14 +54,13 @@ class stream : public http_tsdb::stream { const std::shared_ptr& io_context, const std::shared_ptr& conf, const std::string& account_id, - http_client::client::connection_creator conn_creator = - http_client::http_connection::load); + http::connection_creator conn_creator); const std::string& get_authorization() const { return _authorization; } }; }; // namespace victoria_metrics -} +} // namespace com::centreon::broker #endif // !CCB_VICTORIA_METRICS_STREAM_HH diff --git a/broker/victoria_metrics/src/connector.cc b/broker/victoria_metrics/src/connector.cc index 47bb952138b..a9c06069dab 100644 --- a/broker/victoria_metrics/src/connector.cc +++ b/broker/victoria_metrics/src/connector.cc @@ -1,11 +1,11 @@ /** - * Copyright 2022 Centreon + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,13 @@ * limitations under the License. * * For more information : contact@centreon.com + * */ #include "com/centreon/broker/victoria_metrics/connector.hh" -#include "com/centreon/broker/http_client/https_connection.hh" +#include "com/centreon/broker/log_v2.hh" #include "com/centreon/broker/victoria_metrics/stream.hh" +#include "com/centreon/common/http/https_connection.hh" #include "com/centreon/common/pool.hh" using namespace com::centreon::broker; @@ -37,9 +39,18 @@ connector::connector(const std::shared_ptr& conf, std::shared_ptr connector::open() { if (!_conf->is_crypted()) { return stream::load(com::centreon::common::pool::io_context_ptr(), _conf, - _account_id, http_client::http_connection::load); + _account_id, [conf = _conf]() { + return http::http_connection::load( + com::centreon::common::pool::io_context_ptr(), + log_v2::victoria_metrics(), conf); + }); } else { return stream::load(com::centreon::common::pool::io_context_ptr(), _conf, - _account_id, http_client::https_connection::load); + _account_id, [conf = _conf]() { + return http::https_connection::load( + com::centreon::common::pool::io_context_ptr(), + log_v2::victoria_metrics(), conf, + http::https_connection::load_client_certificate); + }); } } \ No newline at end of file diff --git a/broker/victoria_metrics/src/stream.cc b/broker/victoria_metrics/src/stream.cc index d0000f28819..afe84157706 100644 --- a/broker/victoria_metrics/src/stream.cc +++ b/broker/victoria_metrics/src/stream.cc @@ -1,11 +1,11 @@ /** - * Copyright 2022 Centreon + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,6 +14,7 @@ * limitations under the License. * * For more information : contact@centreon.com + * */ #include "com/centreon/broker/victoria_metrics/stream.hh" @@ -36,7 +37,7 @@ const std::string stream::allowed_macros = stream::stream(const std::shared_ptr& io_context, const std::shared_ptr& conf, const std::string& account_id, - http_client::client::connection_creator conn_creator) + http::connection_creator conn_creator) : http_tsdb::stream("victoria_metrics", io_context, log_v2::victoria_metrics(), @@ -66,7 +67,7 @@ std::shared_ptr stream::load( const std::shared_ptr& io_context, const std::shared_ptr& conf, const std::string& account_id, - http_client::client::connection_creator conn_creator) { + http::connection_creator conn_creator) { return std::shared_ptr( new stream(io_context, conf, account_id, conn_creator)); } diff --git a/broker/victoria_metrics/test/stream_test.cc b/broker/victoria_metrics/test/stream_test.cc index 01fb79bfba5..76c431167ba 100644 --- a/broker/victoria_metrics/test/stream_test.cc +++ b/broker/victoria_metrics/test/stream_test.cc @@ -1,5 +1,5 @@ /** - * Copyright 2019 Centreon (https://www.centreon.com/) + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,9 +31,12 @@ using duration = system_clock::duration; #include "com/centreon/broker/config/applier/state.hh" #include "com/centreon/broker/file/disk_accessor.hh" +#include "com/centreon/broker/log_v2.hh" #include "com/centreon/broker/victoria_metrics/stream.hh" +#include "com/centreon/common/pool.hh" using namespace com::centreon::broker; +using namespace com::centreon::common; using namespace com::centreon::broker::victoria_metrics; using namespace nlohmann; @@ -49,11 +52,16 @@ class victoria_stream_test : public ::testing::Test { }; TEST_F(victoria_stream_test, Authorization) { - http_client::http_config dummy; + http::http_config dummy; std::vector dummy2; auto cfg = std::make_shared( dummy, "/write", "Aladdin", "open sesame", 1, dummy2, dummy2); - std::shared_ptr s = stream::load(g_io_context, cfg, "my_account"); + std::shared_ptr s = + stream::load(g_io_context, cfg, "my_account", [cfg]() { + return http::http_connection::load( + com::centreon::common::pool::io_context_ptr(), + log_v2::victoria_metrics(), cfg); + }); ASSERT_EQ(s->get_authorization(), "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="); } diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index e320764b7b8..5c3de9473bc 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -21,6 +21,7 @@ project("Centreon common" C CXX) # Set directories. set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/inc/com/centreon/common") +set (HTTP_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/http/inc/com/centreon/common/http") set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") set(TEST_DIR "${PROJECT_SOURCE_DIR}/test") @@ -52,6 +53,7 @@ set(SOURCES # Include directories. include_directories("${INCLUDE_DIR}" + ${HTTP_INCLUDE_DIR} ${VCPKG_INCLUDE_DIR} ) @@ -62,6 +64,8 @@ set_property(TARGET centreon_common PROPERTY POSITION_INDEPENDENT_CODE ON) target_precompile_headers(centreon_common PRIVATE precomp_inc/precomp.hh) +add_subdirectory(http) + if(WITH_TESTING) add_subdirectory(test) endif() diff --git a/broker/http_client/CMakeLists.txt b/common/http/CMakeLists.txt similarity index 62% rename from broker/http_client/CMakeLists.txt rename to common/http/CMakeLists.txt index 3696f05ce85..d57e20e73b0 100644 --- a/broker/http_client/CMakeLists.txt +++ b/common/http/CMakeLists.txt @@ -18,32 +18,25 @@ # Global options. -set(INC_DIR "${PROJECT_SOURCE_DIR}/http_client/inc/com/centreon/broker/http_client") +set(INC_DIR "${PROJECT_SOURCE_DIR}/http/inc/com/centreon/common/http") set(SRC_DIR "src") -set(TEST_DIR "${PROJECT_SOURCE_DIR}/http_client/test") -include_directories(${INC_DIR}) +set(TEST_DIR "${PROJECT_SOURCE_DIR}/http/test") # Sources. set(SOURCES ${SRC_DIR}/http_client.cc ${SRC_DIR}/http_connection.cc ${SRC_DIR}/https_connection.cc + ${SRC_DIR}/http_server.cc ) -# Headers. -set(HEADERS - ${INC_DIR}/http_client.hh - ${INC_DIR}/http_connection.hh - ${INC_DIR}/https_connection.hh - ${INC_DIR}/http_config.hh -) -add_library(http_client STATIC ${SOURCES} ${HEADERS}) -target_include_directories(http_client PRIVATE ${INC_DIR}) +add_library(centreon_http STATIC ${SOURCES} ) +target_include_directories(centreon_http PRIVATE ${INC_DIR}) -target_precompile_headers(http_client PRIVATE precomp_inc/precomp.hh) +target_precompile_headers(centreon_http REUSE_FROM centreon_common) -set_target_properties(http_client PROPERTIES COMPILE_FLAGS "-fPIC") +set_target_properties(centreon_http PROPERTIES COMPILE_FLAGS "-fPIC") # Testing. if(WITH_TESTING) diff --git a/common/http/doc/common-http.md b/common/http/doc/common-http.md new file mode 100644 index 00000000000..500f427c8e3 --- /dev/null +++ b/common/http/doc/common-http.md @@ -0,0 +1,223 @@ +# Centreon Common http library documentation + +## Introduction + +This library relies on low level http library: boost::beast. +The goal of this library is to provide a higher level than boost::beast. +It provides http(s) client and server implementation. +All this library is asynchronous. +In order to use it, you have to provide a shared_ptr yet run outside and a shared_ptr. +All is configured in that bean: http_config. +beast::request and beast::response are body templated. We use the boost::beast::http::string_body version +```c++ +using request_type = boost::beast::http::request; +using response_type = boost::beast::http::response; +``` + + +### Common client/server classes + +#### http_config: a bean that contains all configuration options needed by server and client +Fields passed to constructor: +- endpoint: this endpoint is the connection endpoint in case of client or listen endpoint in case of server +- server_name: (not used by this library), but provided to construct http request + ```c++ + auto ret = std::make_shared( + boost::beast::http::verb::post, _conf->get_server_name(), + "action"); + ``` +- connect_timeout: timeout to connect to server (tcp connection time, not handshake) +- send_timeout: timeout to send request (client) or response (server) +- receive_timeout: + - client: timeout to receive response after have sent request + - server: max time to wait a request after connection success or after last response sent. This parameter is also added to response via the keep-alive header. If this parameter is lower than 1s, no keep-alive is added to the response and connection is closed after response sent +- second_tcp_keep_alive_interval: if not null, tcp keep alive is activated on each connection in order to maintain NATs +- max_retry_interval: when client fails to send request, client retries to send request, delay between two attempts is doubled until it reaches max_retry_interval +- max_send_retry: max client send attempts +- default_http_keepalive_duration: When server answers with keep-alive header, but not delay or when where are in http 1.1, this parameter is used by client to disconnect from the server after this delay +- ssl_method: parameter used to initialize ssl context + - Values allowed: + - tlsv1_client + - tlsv1_server + - sslv23 + - sslv23_client + - sslv23_server + - tlsv11 + - tlsv11_client + - tlsv11_server + - tlsv12 + - tlsv12_client + - tlsv12_server + - tlsv13 + - tlsv13_client + - tlsv13_server + - tls + - tls_client + - tls_server +- certificate_path: path of the file that contains certificate (both client and server) +- key_path: path of the file thant contains key of the certificate (server only) + +#### request_base +It inherits from boost::beast::http::request< boost::beast::http::string_body > and embed some time-points to get statistics + + +#### connection_base +This abstract class is the mother of http_connection and https_connection. +It contains an io_context, logger and the peer endpoint +In order to avoid mistakes, it also contains an atomic _state indicator. + +- abstract methods: + - shutdown: shutdown connection, connection won't be reusable after this call. This method returns immediately but is asynchronous in case of https case. So connection object will be deleted later once ssl shutdown have been done. + - server usage + - on_accept(): first, the server creates an non connected connection, when it's accepted, it calls on_accept and then this connection can live. The implemented method must call on_accept(connect_callback_type&& callback) + - on_accept(connect_callback_type&& callback): do the ssl handshake and initialize tcp keep-alive. callback must call receive_request to wait for request + - receive_request(request_callback_type&& callback): wait for incoming request, it must call answer to send response + - answer(const response_ptr& response, answer_callback_type&& callback): send answer to client, if a receive_timeout is > one second, implementation must call receive_request to read nex requests, otherwise, socket is closed adn nothing have to be done + - get_socket: returns a reference to the transport layer (asio::ip::tcp::socket) + - client usage + - connect(connect_callback_type&& callback): It connects to the server and performs ssl handshake. callback can then call send + - send(request_ptr request, send_callback_type&& callback): send request to the server and pass response to callback. On response, connection is closed if server indicates that there is no keep-alive + +- implemented methods: + - gest_keepalive: (client side) It extracts keep-alive header from response and calculate time-point limit after that connection will be shutdown (returned by get_keep_alive_end()). + - add_keep_alive_to_server_response: (server side) It adds keep-alive header to answer. It uses receive_time out config parameter. + +#### http_connection +- init_keep_alive(): activate tcp keep-alive +- load: this object must be created in an shared_ptr (constructor is protected). This method creates it in a shared_ptr + +#### https_connection +- init_keep_alive(): activate tcp keep-alive +- load: this object must be created in an shared_ptr (constructor is protected). This method creates it in a shared_ptr + +#### http_client +This client uses http_connection or https_connection objects to send request to ONE server.
+Connections are stored in three containers: +- _not_connected_conns: not connected connections +- _keep_alive_conns: connected connections maintained by both client and server after have received a response with http keep-alive activated. These connections are available for next requests. +- _busy_conns: connections active +##### strategy +When we want to send a request, we first try to reuse a connection of _keep_alive_conns.
+If not available, we connect one of _not_connected_conns.
+If not available, request is pushed to queue and will be send as soon as a connection will ba available. +##### retry strategy +In case of failure (transport failure not http error code), request sent is deferred with an asio timer and then repushed in queue. Delay between two attempts is doubled on each send failure. + +#### http_server +The job of this class is to only accept incoming connections. Once a connection is accepted, this connection is not owned by http_server object and continue to leave even if http_server has been shutting down. +In order to use it with both http and https, the most simple is to define a session templated class that can inherit either from http_connection or https_connection. +Then you have to implement functors in charge of create of session. +http_server use this functor to create a not connected session. Once it's accepted, it call on_accept() method of the session object, release it and create another session object. + +Example of implementation: +- session implementation +```c++ +template +class session_test : public connection_class { + + void wait_for_request(); + + void answer(const std::shared_ptr& request); + + public: + session_test(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_initializer) + : connection_class(io_context, logger, conf, ssl_initializer) { + } + + std::shared_ptr shared_from_this() { + return std::static_pointer_cast( + connection_class::shared_from_this()); + } + + void on_accept() override; +}; + +/** + * @brief one tcp connection is accepted, start handshake + * + * @tparam connection_class + */ +template +void session_test::on_accept() { + connection_class::on_accept( + [me = shared_from_this()](const boost::beast::error_code& err, + const std::string&) { + if (!err) + me->wait_for_request(); + }); +} + +/** + * @brief wait incoming request + * + * @tparam connection_class + */ +template +void session_test::wait_for_request() { + connection_class::receive_request( + [me = shared_from_this()](const boost::beast::error_code& err, + const std::string& detail, + const std::shared_ptr& request) { + if (err) { + SPDLOG_LOGGER_DEBUG(me->_logger, + "fail to receive request from {}: {}", me->_peer, + err.what()); + return; + } + me->answer(request); + }); +} + +/** + * @brief called once a request have been received + * Once the response has been sent, it calls wait_for_request + * to manage next incoming request + * + * @tparam connection_class + * @param request + */ +template +void session_test::answer( + const std::shared_ptr& request) { + response_ptr resp(std::make_shared()); + resp->version(request->version()); + resp->body() = request->body(); + resp->content_length(resp->body().length()); + + connection_class::answer( + resp, [me = shared_from_this(), resp](const boost::beast::error_code& err, + const std::string& detail) { + if (err) { + SPDLOG_LOGGER_ERROR(me->_logger, "fail to answer to client {} {}", + err.message(), detail); + return; + } + me->wait_for_request(); + }); +} +``` +- server creation +```c++ +void create_server(const std::shared_ptr & io_ctx, + const std::shared_ptr logger, + const http_config::pointer conf) { + connection_creator server_creator; + if (conf->is_crypted()) { + server_creator = [io_ctx, logger, conf]() { + return std::make_shared>( + io_ctx, logger, conf, https_connection::load_server_certificate); + }; + } + else { + server_creator = [io_ctx, logger, conf]() { + return std::make_shared>( + io_ctx, logger, conf); + }; + } + auto server = http_server::load(io_ctx, logger, conf, + std::move(server_creator)); +} +``` diff --git a/broker/http_client/inc/com/centreon/broker/http_client/http_client.hh b/common/http/inc/com/centreon/common/http/http_client.hh similarity index 78% rename from broker/http_client/inc/com/centreon/broker/http_client/http_client.hh rename to common/http/inc/com/centreon/common/http/http_client.hh index 8ceb626f325..4bbe4a1f6c2 100644 --- a/broker/http_client/inc/com/centreon/broker/http_client/http_client.hh +++ b/common/http/inc/com/centreon/common/http/http_client.hh @@ -1,20 +1,21 @@ -/* -** Copyright 2022 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #ifndef CCB_HTTP_CLIENT_CLIENT_HH__ #define CCB_HTTP_CLIENT_CLIENT_HH__ @@ -23,12 +24,10 @@ class client_test; -namespace com::centreon::broker { +namespace com::centreon::common::http { -namespace http_client { /** * @class client - * "broker/http_client/inc/com/centreon/broker/http_client/http_client.hh" * * @brief this class is the heart of the library * it dispatchs requests on connections to the server @@ -48,11 +47,6 @@ namespace http_client { */ class client : public std::enable_shared_from_this { public: - using connection_creator = std::function& io_context, - const std::shared_ptr& logger, - const http_config::pointer& conf)>; - friend client_test; private: @@ -120,7 +114,7 @@ class client : public std::enable_shared_from_this { static pointer load(const std::shared_ptr& io_context, const std::shared_ptr& logger, const http_config::pointer& conf, - connection_creator conn_creator = http_connection::load); + connection_creator conn_creator); template bool send(const request_ptr& request, callback_type&& callback) { @@ -146,9 +140,6 @@ void client::visit_queue(visitor_type& visitor) const { visitor(*cb->request); } } - -} // namespace http_client - -} +} // namespace com::centreon::common::http #endif // CCB_HTTP_CLIENT_CLIENT_HH__ diff --git a/broker/http_client/inc/com/centreon/broker/http_client/http_config.hh b/common/http/inc/com/centreon/common/http/http_config.hh similarity index 81% rename from broker/http_client/inc/com/centreon/broker/http_client/http_config.hh rename to common/http/inc/com/centreon/common/http/http_config.hh index c24c82b2005..90c0bce3aa8 100644 --- a/broker/http_client/inc/com/centreon/broker/http_client/http_config.hh +++ b/common/http/inc/com/centreon/common/http/http_config.hh @@ -1,33 +1,33 @@ -/* -** Copyright 2022 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #ifndef CCB_HTTP_CLIENT_CONFIG_HH__ #define CCB_HTTP_CLIENT_CONFIG_HH__ -namespace com::centreon::broker { +namespace com::centreon::common::http { -namespace http_client { /** * @brief this class is a bean that contains all config parameters * */ class http_config { - // destination address + // destination or listen address asio::ip::tcp::endpoint _endpoint; std::string _server_name; bool _crypted; @@ -99,13 +99,11 @@ class http_config { const std::string& get_certificate_path() const { return _certificate_path; } }; -}; // namespace http_client - -} +} // namespace com::centreon::common::http namespace fmt { template <> -struct formatter { +struct formatter { constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { return ctx.begin(); } @@ -113,7 +111,7 @@ struct formatter { // Formats the point p using the parsed format specification (presentation) // stored in this formatter. template - auto format(const com::centreon::broker::http_client::http_config& conf, + auto format(const com::centreon::common::http::http_config& conf, FormatContext& ctx) const -> decltype(ctx.out()) { std::ostringstream s; s << conf.get_endpoint(); diff --git a/broker/http_client/inc/com/centreon/broker/http_client/http_connection.hh b/common/http/inc/com/centreon/common/http/http_connection.hh similarity index 61% rename from broker/http_client/inc/com/centreon/broker/http_client/http_connection.hh rename to common/http/inc/com/centreon/common/http/http_connection.hh index 198405214ab..840d5f32c41 100644 --- a/broker/http_client/inc/com/centreon/broker/http_client/http_connection.hh +++ b/common/http/inc/com/centreon/common/http/http_connection.hh @@ -1,29 +1,29 @@ -/* -** Copyright 2022 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #ifndef CCB_HTTP_CLIENT_CONNECTION_HH__ #define CCB_HTTP_CLIENT_CONNECTION_HH__ #include "http_config.hh" -namespace com::centreon::broker { +namespace com::centreon::common::http { -namespace http_client { /** * @brief we use a std::string body * @@ -50,6 +50,22 @@ using send_callback_type = std::function; +/** + * @brief called in server mode when response have been sent + * + */ +using answer_callback_type = + std::function; + +/** + * @brief called after a request have been received (server mode) + * + */ +using request_callback_type = + std::function&)>; + /** * @brief this option set the interval in seconds between two keepalive sent * @@ -107,7 +123,9 @@ using request_ptr = std::shared_ptr; */ class connection_base : public std::enable_shared_from_this { protected: - // this atomic uint is used to atomicaly modify object state + asio::ip::tcp::endpoint _peer; + + // this atomic uint is used to atomically modify object state std::atomic_uint _state; // http keepalive expiration time_point _keep_alive_end; @@ -152,20 +170,58 @@ class connection_base : public std::enable_shared_from_this { virtual void connect(connect_callback_type&& callback) = 0; + // client version virtual void send(request_ptr request, send_callback_type&& callback) = 0; + // server version + /** + * @brief to override in accepted session objects + * It has to call on_accept(connect_callback_type&& callback) in order to + * receive + * + */ + virtual void on_accept() {} + + virtual void on_accept(connect_callback_type&& callback) = 0; + + virtual void answer(const response_ptr& response, + answer_callback_type&& callback) = 0; + virtual void receive_request(request_callback_type&& callback) = 0; + e_state get_state() const { return e_state(_state.load()); } time_point get_keep_alive_end() const { return _keep_alive_end; } void gest_keepalive(const response_ptr& response); + + virtual asio::ip::tcp::socket& get_socket() = 0; + + asio::ip::tcp::endpoint& get_peer() { return _peer; } + const asio::ip::tcp::endpoint& get_peer() const { return _peer; } }; +/** + * @brief This initializer is used by https client or server connection to init + * ssl context In order to have the same signature, this parameter is passed to + * http_connection::load with nullptr as default value even if it is not + * used + * + */ +using ssl_ctx_initializer = + std::function; + +/** + * @brief this functor is used by client and server to create needed connection + * this constructor allow server and client to work either with http or https + * + */ +using connection_creator = std::function; + /** * @brief this class manages a tcp connection * it's used by client object to send request * constructor is protected because load is mandatory - * this object musn't be create on stack and must be allocated + * this object mustn't be create on stack and must be allocated * */ class http_connection : public connection_base { @@ -174,11 +230,14 @@ class http_connection : public connection_base { protected: http_connection(const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf); + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_initializer = nullptr); void on_connect(const boost::beast::error_code& err, const connect_callback_type& callback); + void init_keep_alive(); + void on_sent(const boost::beast::error_code& err, request_ptr request, send_callback_type& callback); @@ -196,7 +255,8 @@ class http_connection : public connection_base { static pointer load(const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf); + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_initializer = nullptr); ~http_connection(); @@ -205,24 +265,30 @@ class http_connection : public connection_base { void connect(connect_callback_type&& callback) override; void send(request_ptr request, send_callback_type&& callback) override; -}; -} // namespace http_client + void on_accept(connect_callback_type&& callback) override; -} + void answer(const response_ptr& response, + answer_callback_type&& callback) override; + void receive_request(request_callback_type&& callback) override; + + asio::ip::tcp::socket& get_socket() override; +}; + +} // namespace com::centreon::common::http namespace fmt { template <> -struct formatter +struct formatter : ostream_formatter {}; template <> -struct formatter +struct formatter : ostream_formatter {}; template <> -struct formatter +struct formatter : ostream_formatter {}; } // namespace fmt diff --git a/common/http/inc/com/centreon/common/http/http_server.hh b/common/http/inc/com/centreon/common/http/http_server.hh new file mode 100644 index 00000000000..7c9d680083b --- /dev/null +++ b/common/http/inc/com/centreon/common/http/http_server.hh @@ -0,0 +1,68 @@ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCB_HTTP_SERVER_HH__ +#define CCB_HTTP_SERVER_HH__ + +#include "http_config.hh" +#include "http_connection.hh" + +namespace com::centreon::common::http { + +/** + * @brief This class is an http(s) server + * the difference between http and https server is the connection builder passed + * in constructor parameter + * + */ +class server : public std::enable_shared_from_this { + private: + const std::shared_ptr _io_context; + const std::shared_ptr _logger; + http_config::pointer _conf; + connection_creator _conn_creator; + asio::ip::tcp::acceptor _acceptor; + std::mutex _acceptor_m; + + void start_accept(); + + void on_accept(const boost::beast::error_code& err, + const connection_base::pointer& conn); + + public: + using pointer = std::shared_ptr; + + server(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const http_config::pointer& conf, + connection_creator&& conn_creator); + + ~server(); + + static pointer load(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const http_config::pointer& conf, + connection_creator&& conn_creator); + + void shutdown(); +}; + +} // namespace com::centreon::common::http + +#endif diff --git a/broker/http_client/inc/com/centreon/broker/http_client/https_connection.hh b/common/http/inc/com/centreon/common/http/https_connection.hh similarity index 57% rename from broker/http_client/inc/com/centreon/broker/http_client/https_connection.hh rename to common/http/inc/com/centreon/common/http/https_connection.hh index 1fff51f1e09..049b5824735 100644 --- a/broker/http_client/inc/com/centreon/broker/http_client/https_connection.hh +++ b/common/http/inc/com/centreon/common/http/https_connection.hh @@ -1,29 +1,29 @@ -/* -** Copyright 2022 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #ifndef CCB_HTTPS_CLIENT_CONNECTION_HH__ #define CCB_HTTPS_CLIENT_CONNECTION_HH__ #include "http_connection.hh" -namespace com::centreon::broker { +namespace com::centreon::common::http { -namespace http_client { /** * @brief https version of http_connection * interface is the same @@ -42,7 +42,8 @@ class https_connection : public connection_base { https_connection(const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf); + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_init); void on_handshake(const boost::beast::error_code err, const connect_callback_type& callback); @@ -50,6 +51,8 @@ class https_connection : public connection_base { void on_connect(const boost::beast::error_code& err, connect_callback_type& callback); + void init_keep_alive(); + void on_sent(const boost::beast::error_code& err, request_ptr request, send_callback_type& callback); @@ -67,7 +70,9 @@ class https_connection : public connection_base { static pointer load(const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf); + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_init = + https_connection::load_client_certificate); ~https_connection(); @@ -76,10 +81,19 @@ class https_connection : public connection_base { void connect(connect_callback_type&& callback) override; void send(request_ptr request, send_callback_type&& callback) override; -}; -} // namespace http_client + void on_accept(connect_callback_type&& callback) override; + + void answer(const response_ptr& response, + answer_callback_type&& callback) override; + void receive_request(request_callback_type&& callback) override; + + asio::ip::tcp::socket& get_socket() override; + + static void load_client_certificate(asio::ssl::context& ctx, + const http_config::pointer& conf); +}; -} +} // namespace com::centreon::common::http #endif // CCB_HTTPS_CLIENT_CONNEXION_HH__ diff --git a/broker/http_client/src/http_client.cc b/common/http/src/http_client.cc similarity index 98% rename from broker/http_client/src/http_client.cc rename to common/http/src/http_client.cc index 2fbcbf060b9..fee72c2c674 100644 --- a/broker/http_client/src/http_client.cc +++ b/common/http/src/http_client.cc @@ -1,5 +1,5 @@ /** - * Copyright 2022 Centreon (https://www.centreon.com/) + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,11 @@ * */ -#include "com/centreon/common/defer.hh" +#include "defer.hh" #include "http_client.hh" -using namespace com::centreon::broker; -using namespace com::centreon::broker::http_client; +using namespace com::centreon::common::http; using lock_guard = std::lock_guard; @@ -52,7 +51,7 @@ client::client(const std::shared_ptr& io_context, _busy_conns.reserve(conf->get_max_connections()); // create all connection ready to connect for (unsigned cpt = 0; cpt < conf->get_max_connections(); ++cpt) { - _not_connected_conns.insert(conn_creator(io_context, logger, conf)); + _not_connected_conns.insert(conn_creator()); } } diff --git a/broker/http_client/src/http_connection.cc b/common/http/src/http_connection.cc similarity index 69% rename from broker/http_client/src/http_connection.cc rename to common/http/src/http_connection.cc index eabdc412d8a..8f7ce8f6e52 100644 --- a/broker/http_client/src/http_connection.cc +++ b/common/http/src/http_connection.cc @@ -1,5 +1,5 @@ /** - * Copyright 2022 Centreon (https://www.centreon.com/) + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ #include "http_connection.hh" -using namespace com::centreon::broker; -using namespace com::centreon::broker::http_client; +using namespace com::centreon::common::http; std::string connection_base::state_to_str(unsigned state) { switch (state) { @@ -112,7 +111,8 @@ void connection_base::gest_keepalive(const response_ptr& resp) { http_connection::http_connection( const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf) + const http_config::pointer& conf, + const ssl_ctx_initializer&) : connection_base(io_context, logger, conf), _socket(boost::beast::net::make_strand(*io_context)) { SPDLOG_LOGGER_DEBUG(_logger, "create http_connection {:p} to {}", @@ -136,7 +136,8 @@ http_connection::~http_connection() { http_connection::pointer http_connection::load( const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf) { + const http_config::pointer& conf, + const ssl_ctx_initializer&) { return pointer(new http_connection(io_context, logger, conf)); } @@ -173,6 +174,7 @@ void http_connection::connect(connect_callback_type&& callback) { /** * @brief connect handler + * in case of success, it initializes tcp keepalive * * @param err * @param callback @@ -193,34 +195,63 @@ void http_connection::on_connect(const boost::beast::error_code& err, BAD_CONNECT_STATE_ERROR("on_connect to {}, bad state {}"); } + { + std::lock_guard l(_socket_m); + boost::system::error_code ec; + _peer = _socket.socket().remote_endpoint(ec); + init_keep_alive(); + } + SPDLOG_LOGGER_DEBUG(_logger, "{:p} connected to {}", static_cast(this), + _peer); + + callback(err, {}); +} + +/** + * @brief to call in case of we are an http server + * it initialize http keepalive + * callback is useless in this case but is mandatory to have the same interface + * than https_connection + * + * @param callback called via io_context::post + */ +void http_connection::on_accept(connect_callback_type&& callback) { + unsigned expected = e_not_connected; + if (!_state.compare_exchange_strong(expected, e_idle)) { + BAD_CONNECT_STATE_ERROR("on_tcp_connect to {}, bad state {}"); + } + + std::lock_guard l(_socket_m); + init_keep_alive(); + + SPDLOG_LOGGER_DEBUG(_logger, "{:p} accepted from {}", + static_cast(this), _peer); + + _io_context->post([cb = std::move(callback)]() { cb({}, ""); }); +} + +void http_connection::init_keep_alive() { // we put first keepalive option and then keepalive intervals // system default interval are 7200s witch is too long to maintain a NAT boost::system::error_code err_keep_alive; asio::socket_base::keep_alive opt1(true); - { - std::lock_guard l(_socket_m); - _socket.socket().set_option(opt1, err_keep_alive); + _socket.socket().set_option(opt1, err_keep_alive); + if (err_keep_alive) { + SPDLOG_LOGGER_ERROR(_logger, "fail to activate keep alive for {}", *_conf); + } else { + tcp_keep_alive_interval opt2(_conf->get_second_tcp_keep_alive_interval()); + _socket.socket().set_option(opt2, err_keep_alive); if (err_keep_alive) { - SPDLOG_LOGGER_ERROR(_logger, "fail to activate keep alive for {}", + SPDLOG_LOGGER_ERROR(_logger, "fail to modify keep alive interval for {}", *_conf); - } else { - tcp_keep_alive_interval opt2(_conf->get_second_tcp_keep_alive_interval()); - _socket.socket().set_option(opt2, err_keep_alive); - if (err_keep_alive) { - SPDLOG_LOGGER_ERROR( - _logger, "fail to modify keep alive interval for {}", *_conf); - } - tcp_keep_alive_idle opt3(_conf->get_second_tcp_keep_alive_interval()); - _socket.socket().set_option(opt3, err_keep_alive); - if (err_keep_alive) { - SPDLOG_LOGGER_ERROR( - _logger, "fail to modify first keep alive delay for {}", *_conf); - } } - SPDLOG_LOGGER_DEBUG(_logger, "{:p} connected to {}", - static_cast(this), _conf->get_endpoint()); + tcp_keep_alive_idle opt3(_conf->get_second_tcp_keep_alive_interval()); + _socket.socket().set_option(opt3, err_keep_alive); + if (err_keep_alive) { + SPDLOG_LOGGER_ERROR( + _logger, "fail to modify first keep alive delay for {}", *_conf); + } } - callback(err, {}); } #define BAD_SEND_STATE_ERROR(error_string) \ @@ -248,12 +279,12 @@ void http_connection::send(request_ptr request, send_callback_type&& callback) { request->_send = system_clock::now(); if (_logger->level() == spdlog::level::trace) { - SPDLOG_LOGGER_TRACE( - _logger, "{:p} send request {} to {}", static_cast(this), - static_cast(*request), _conf->get_endpoint()); + SPDLOG_LOGGER_TRACE(_logger, "{:p} send request {} to {}", + static_cast(this), + static_cast(*request), _peer); } else { SPDLOG_LOGGER_DEBUG(_logger, "{:p} send request to {}", - static_cast(this), _conf->get_endpoint()); + static_cast(this), _peer); } std::lock_guard l(_socket_m); _socket.expires_after(_conf->get_send_timeout()); @@ -353,6 +384,80 @@ void http_connection::on_read(const boost::beast::error_code& err, callback(err, {}, resp); } +void http_connection::answer(const response_ptr& response, + answer_callback_type&& callback) { + unsigned expected = e_idle; + if (!_state.compare_exchange_strong(expected, e_send)) { + std::string detail = fmt::format( + "{:p}" + "answer to {}, bad state {}", + static_cast(this), _peer, state_to_str(expected)); + SPDLOG_LOGGER_ERROR(_logger, detail); + _io_context->post([cb = std::move(callback), detail]() { + cb(std::make_error_code(std::errc::invalid_argument), detail); + }); + return; + } + + std::lock_guard l(_socket_m); + _socket.expires_after(_conf->get_send_timeout()); + boost::beast::http::async_write( + _socket, *response, + [me = shared_from_this(), response, cb = std::move(callback)]( + const boost::beast::error_code& ec, size_t) mutable { + if (ec) { + SPDLOG_LOGGER_ERROR(me->_logger, + "{:p} fail to send response {} to {}: {}", + static_cast(me.get()), *response, + me->get_peer(), ec.message()); + me->shutdown(); + } else { + unsigned expected = e_send; + me->_state.compare_exchange_strong(expected, e_idle); + } + SPDLOG_LOGGER_TRACE(me->_logger, "{:p} response sent: {}", + static_cast(me.get()), *response); + cb(ec, ""); + }); +} + +void http_connection::receive_request(request_callback_type&& callback) { + unsigned expected = e_idle; + if (!_state.compare_exchange_strong(expected, e_receive)) { + std::string detail = fmt::format( + "{:p}" + "receive_request from {}, bad state {}", + static_cast(this), _peer, state_to_str(expected)); + SPDLOG_LOGGER_ERROR(_logger, detail); + _io_context->post([cb = std::move(callback), detail]() { + cb(std::make_error_code(std::errc::invalid_argument), detail, + std::shared_ptr()); + }); + return; + } + + std::lock_guard l(_socket_m); + _socket.expires_after(_conf->get_receive_timeout()); + auto req = std::make_shared(); + boost::beast::http::async_read( + _socket, _recv_buffer, *req, + [me = shared_from_this(), req, cb = std::move(callback)]( + const boost::beast::error_code& ec, std::size_t) mutable { + if (ec) { + SPDLOG_LOGGER_ERROR( + me->_logger, "{:p} fail to receive request from {}: {}", + static_cast(me.get()), me->get_peer(), ec.message()); + me->shutdown(); + } else { + unsigned expected = e_receive; + me->_state.compare_exchange_strong(expected, e_idle); + } + SPDLOG_LOGGER_TRACE(me->_logger, "{:p} receive: {}", + static_cast(me.get()), *req); + cb(ec, "", req); + }); +} + /** * @brief shutdown socket and close * @@ -366,3 +471,12 @@ void http_connection::shutdown() { _socket.socket().shutdown(boost::asio::socket_base::shutdown_both, err); _socket.close(); } + +/** + * @brief get underlay socket + * + * @return asio::ip::tcp::socket& + */ +asio::ip::tcp::socket& http_connection::get_socket() { + return _socket.socket(); +} diff --git a/common/http/src/http_server.cc b/common/http/src/http_server.cc new file mode 100644 index 00000000000..16bfa336d36 --- /dev/null +++ b/common/http/src/http_server.cc @@ -0,0 +1,95 @@ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include "http_server.hh" + +using namespace com::centreon::common::http; + +server::server(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const http_config::pointer& conf, + connection_creator&& conn_creator) + : _io_context(io_context), + _logger(logger), + _conf(conf), + _conn_creator(conn_creator), + _acceptor(*io_context) { + _acceptor.open(_conf->get_endpoint().protocol()); + _acceptor.set_option(asio::ip::tcp::socket::reuse_address(true)); + + // Bind to the server address + _acceptor.bind(_conf->get_endpoint()); + // Start listening for connections + _acceptor.listen(asio::ip::tcp::socket::max_listen_connections); +} + +server::~server() { + shutdown(); +} + +server::pointer server::load( + const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const http_config::pointer& conf, + connection_creator&& conn_creator) { + std::shared_ptr ret = std::make_shared( + io_context, logger, conf, std::move(conn_creator)); + + ret->start_accept(); + + SPDLOG_LOGGER_INFO(logger, "server listen on {}", conf->get_endpoint()); + + return ret; +} + +void server::start_accept() { + connection_base::pointer future_conn = _conn_creator(); + std::lock_guard l(_acceptor_m); + _acceptor.async_accept(future_conn->get_socket(), future_conn->get_peer(), + [future_conn, me = shared_from_this()]( + const boost::beast::error_code& err) { + me->on_accept(err, future_conn); + }); +} + +void server::on_accept(const boost::beast::error_code& err, + const connection_base::pointer& conn) { + if (err) { + SPDLOG_LOGGER_ERROR(_logger, "fail accept connection on {}: {}", + _conf->get_endpoint(), err.message()); + return; + } + SPDLOG_LOGGER_DEBUG(_logger, "connection accepted from {} to {}", + conn->get_peer(), _conf->get_endpoint()); + conn->on_accept(); + + start_accept(); +} + +void server::shutdown() { + std::lock_guard l(_acceptor_m); + boost::system::error_code ec; + _acceptor.close(ec); + if (ec) { + SPDLOG_LOGGER_ERROR(_logger, "error at server shutdown on {}: {}", + _conf->get_endpoint(), ec.message()); + } else { + SPDLOG_LOGGER_INFO(_logger, "server shutdown {}", _conf->get_endpoint()); + } +} diff --git a/broker/http_client/src/https_connection.cc b/common/http/src/https_connection.cc similarity index 65% rename from broker/http_client/src/https_connection.cc rename to common/http/src/https_connection.cc index b6928f6f07a..7e2e68e59d3 100644 --- a/broker/http_client/src/https_connection.cc +++ b/common/http/src/https_connection.cc @@ -1,5 +1,5 @@ /** - * Copyright 2022 Centreon (https://www.centreon.com/) + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,15 +23,13 @@ #include "https_connection.hh" -using namespace com::centreon::broker; using namespace com::centreon::exceptions; -using namespace com::centreon::broker::http_client; +using namespace com::centreon::common::http; namespace beast = boost::beast; -namespace com::centreon::broker { +namespace com::centreon::common::http::detail { -namespace http_client::detail { /** * @brief to avoid read certificate on each request, this singleton do the job * it maintains a certificate cache @@ -119,9 +117,7 @@ void certificate_cache::clean() { } } -}; // namespace http_client::detail - -} // namespace com::centreon::broker +} // namespace com::centreon::common::http::detail /** * @brief Construct a new https connection::https connection object @@ -134,23 +130,20 @@ void certificate_cache::clean() { https_connection::https_connection( const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf) + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_init) : connection_base(io_context, logger, conf), - _sslcontext(conf->get_ssl_method()), - _stream(std::make_unique(beast::net::make_strand(*io_context), - _sslcontext)) { - if (!_conf->get_certificate_path().empty()) { - std::shared_ptr cert_content = - detail::certificate_cache::get_mutable_instance().get_certificate( - _conf->get_certificate_path()); - _sslcontext.add_certificate_authority( - asio::buffer(cert_content->c_str(), cert_content->length())); - } - SPDLOG_LOGGER_DEBUG(_logger, "create https_connection to {}", *conf); + _sslcontext(conf->get_ssl_method()) { + ssl_init(_sslcontext, conf); + _stream = std::make_unique(beast::net::make_strand(*io_context), + _sslcontext); + SPDLOG_LOGGER_DEBUG(_logger, "create https_connection {:p} to {}", + static_cast(this), *conf); } https_connection::~https_connection() { - SPDLOG_LOGGER_DEBUG(_logger, "delete https_connection to {}", *_conf); + SPDLOG_LOGGER_DEBUG(_logger, "delete https_connection {:p} to {}", + static_cast(this), *_conf); } /** @@ -164,8 +157,9 @@ https_connection::~https_connection() { https_connection::pointer https_connection::load( const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_config::pointer& conf) { - return pointer(new https_connection(io_context, logger, conf)); + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_init) { + return pointer(new https_connection(io_context, logger, conf, ssl_init)); } #define BAD_CONNECT_STATE_ERROR(error_string) \ @@ -188,7 +182,8 @@ void https_connection::connect(connect_callback_type&& callback) { BAD_CONNECT_STATE_ERROR("can connect to {}, bad state {}"); } - SPDLOG_LOGGER_DEBUG(_logger, "connect to {}", *_conf); + SPDLOG_LOGGER_DEBUG(_logger, "{:p} connect to {}", static_cast(this), + *_conf); std::lock_guard l(_socket_m); beast::get_lowest_layer(*_stream).expires_after(_conf->get_connect_timeout()); beast::get_lowest_layer(*_stream).async_connect( @@ -219,6 +214,22 @@ void https_connection::on_connect(const beast::error_code& err, } std::lock_guard l(_socket_m); + + boost::system::error_code ec; + _peer = beast::get_lowest_layer(*_stream).socket().remote_endpoint(ec); + + init_keep_alive(); + SPDLOG_LOGGER_DEBUG(_logger, "{:p} connected to {}", static_cast(this), + _peer); + + // Perform the SSL handshake + _stream->async_handshake( + asio::ssl::stream_base::client, + [me = shared_from_this(), cb = std::move(callback)]( + const beast::error_code& err) { me->on_handshake(err, cb); }); +} + +void https_connection::init_keep_alive() { boost::system::error_code err_keep_alive; asio::socket_base::keep_alive opt1(true); beast::get_lowest_layer(*_stream).socket().set_option(opt1, err_keep_alive); @@ -238,11 +249,27 @@ void https_connection::on_connect(const beast::error_code& err, _logger, "fail to modify first keep alive delay for {}", *_conf); } } - SPDLOG_LOGGER_DEBUG(_logger, "connected to {}", _conf->get_endpoint()); +} + +/** + * @brief to call in case of we are an http server + * it initialize http keepalive and launch ssl handshake + * + * @param callback + */ +void https_connection::on_accept(connect_callback_type&& callback) { + unsigned expected = e_not_connected; + if (!_state.compare_exchange_strong(expected, e_handshake)) { + BAD_CONNECT_STATE_ERROR("on_tcp_connect to {}, bad state {}"); + } + SPDLOG_LOGGER_DEBUG(_logger, "{:p} accepted from {}", + static_cast(this), _peer); + std::lock_guard l(_socket_m); + init_keep_alive(); // Perform the SSL handshake _stream->async_handshake( - asio::ssl::stream_base::client, + asio::ssl::stream_base::server, [me = shared_from_this(), cb = std::move(callback)]( const beast::error_code& err) { me->on_handshake(err, cb); }); } @@ -256,8 +283,8 @@ void https_connection::on_connect(const beast::error_code& err, void https_connection::on_handshake(const beast::error_code err, const connect_callback_type& callback) { if (err) { - std::string detail = fmt::format("fail handshake to {}: {}", - _conf->get_endpoint(), err.message()); + std::string detail = + fmt::format("fail handshake with {}: {}", _peer, err.message()); SPDLOG_LOGGER_ERROR(_logger, detail); callback(err, detail); shutdown(); @@ -266,9 +293,10 @@ void https_connection::on_handshake(const beast::error_code err, unsigned expected = e_handshake; if (!_state.compare_exchange_strong(expected, e_idle)) { - BAD_CONNECT_STATE_ERROR("on_handshake to {}, bad state {}"); + BAD_CONNECT_STATE_ERROR("on_handshake with {}, bad state {}"); } - SPDLOG_LOGGER_DEBUG(_logger, "handshake done to {}", _conf->get_endpoint()); + SPDLOG_LOGGER_DEBUG(_logger, "{:p} handshake done with {}", + static_cast(this), _peer); callback(err, {}); } @@ -297,11 +325,12 @@ void https_connection::send(request_ptr request, request->_send = system_clock::now(); if (_logger->level() == spdlog::level::trace) { - SPDLOG_LOGGER_TRACE(_logger, "send request {} to {}", - static_cast(*request), - _conf->get_endpoint()); + SPDLOG_LOGGER_TRACE(_logger, "{:p} send request {} to {}", + static_cast(this), + static_cast(*request), _peer); } else { - SPDLOG_LOGGER_DEBUG(_logger, "send request to {}", _conf->get_endpoint()); + SPDLOG_LOGGER_DEBUG(_logger, "{:p} send request to {}", + static_cast(this), _peer); } std::lock_guard l(_socket_m); @@ -388,13 +417,88 @@ void https_connection::on_read(const beast::error_code& err, SPDLOG_LOGGER_ERROR(_logger, "err response for {} \n\n {}", static_cast(*request), *resp); } else { - SPDLOG_LOGGER_DEBUG(_logger, "recv response from {} {}", *_conf, *resp); + SPDLOG_LOGGER_DEBUG(_logger, "{:p} recv response from {} {}", + static_cast(this), *_conf, *resp); } gest_keepalive(resp); callback(err, {}, resp); } +void https_connection::answer(const response_ptr& response, + answer_callback_type&& callback) { + unsigned expected = e_idle; + if (!_state.compare_exchange_strong(expected, e_send)) { + std::string detail = fmt::format( + "{:p}" + "answer to {}, bad state {}", + static_cast(this), _peer, state_to_str(expected)); + SPDLOG_LOGGER_ERROR(_logger, detail); + _io_context->post([cb = std::move(callback), detail]() { + cb(std::make_error_code(std::errc::invalid_argument), detail); + }); + return; + } + + std::lock_guard l(_socket_m); + beast::get_lowest_layer(*_stream).expires_after(_conf->get_send_timeout()); + beast::http::async_write( + *_stream, *response, + [me = shared_from_this(), response, cb = std::move(callback)]( + const boost::beast::error_code& ec, size_t) mutable { + if (ec) { + SPDLOG_LOGGER_ERROR(me->_logger, + "{:p} fail to send response {} to {}: {}", + static_cast(me.get()), *response, + me->get_peer(), ec.message()); + me->shutdown(); + } else { + unsigned expected = e_send; + me->_state.compare_exchange_strong(expected, e_idle); + } + SPDLOG_LOGGER_TRACE(me->_logger, "{:p} response sent: {}", + static_cast(me.get()), *response); + cb(ec, ""); + }); +} + +void https_connection::receive_request(request_callback_type&& callback) { + unsigned expected = e_idle; + if (!_state.compare_exchange_strong(expected, e_receive)) { + std::string detail = fmt::format( + "{:p}" + "receive_request from {}, bad state {}", + static_cast(this), _peer, state_to_str(expected)); + SPDLOG_LOGGER_ERROR(_logger, detail); + _io_context->post([cb = std::move(callback), detail]() { + cb(std::make_error_code(std::errc::invalid_argument), detail, + std::shared_ptr()); + }); + return; + } + + std::lock_guard l(_socket_m); + beast::get_lowest_layer(*_stream).expires_after(_conf->get_receive_timeout()); + auto req = std::make_shared(); + beast::http::async_read( + *_stream, _recv_buffer, *req, + [me = shared_from_this(), req, cb = std::move(callback)]( + const boost::beast::error_code& ec, std::size_t) mutable { + if (ec) { + SPDLOG_LOGGER_ERROR( + me->_logger, "{:p} fail to receive request from {}: {}", + static_cast(me.get()), me->get_peer(), ec.message()); + me->shutdown(); + } else { + unsigned expected = e_receive; + me->_state.compare_exchange_strong(expected, e_idle); + } + SPDLOG_LOGGER_TRACE(me->_logger, "{:p} receive: {}", + static_cast(me.get()), *req); + cb(ec, "", req); + }); +} + /** * @brief perform an async shutdown * unlike http_connection synchronous shutdown can't be done @@ -403,7 +507,8 @@ void https_connection::on_read(const beast::error_code& err, void https_connection::shutdown() { unsigned old_state = _state.exchange(e_shutdown); if (old_state != e_shutdown) { - SPDLOG_LOGGER_DEBUG(_logger, "begin shutdown {}", *_conf); + SPDLOG_LOGGER_DEBUG(_logger, "{:p} begin shutdown {}", + static_cast(this), *_conf); _state = e_shutdown; std::lock_guard l(_socket_m); _stream->async_shutdown( @@ -423,3 +528,39 @@ void https_connection::shutdown() { }); } } + +/** + * @brief get underlay socket + * + * @return asio::ip::tcp::socket& + */ +asio::ip::tcp::socket& https_connection::get_socket() { + return beast::get_lowest_layer(*_stream).socket(); +} + +/** + * @brief This helper can be passed to https_connection constructor fourth param + * when implementing https client as: + * @code {.c++} + * auto conn = https_connection::load(g_io_context, logger, + * conf,[conf](asio::ssl::context& ctx) { + * https_connection::load_client_certificate(ctx, conf); + * }); + * + * @endcode + * + * + * @param ctx + * @param conf + */ +void https_connection::load_client_certificate( + asio::ssl::context& ctx, + const http_config::pointer& conf) { + if (!conf->get_certificate_path().empty()) { + std::shared_ptr cert_content = + detail::certificate_cache::get_mutable_instance().get_certificate( + conf->get_certificate_path()); + ctx.add_certificate_authority( + asio::buffer(cert_content->c_str(), cert_content->length())); + } +} diff --git a/broker/http_client/test/http_client_test.cc b/common/http/test/http_client_test.cc similarity index 80% rename from broker/http_client/test/http_client_test.cc rename to common/http/test/http_client_test.cc index 4c04506558d..280d03f3571 100644 --- a/broker/http_client/test/http_client_test.cc +++ b/common/http/test/http_client_test.cc @@ -1,5 +1,5 @@ /** - * Copyright 2022 Centreon (https://www.centreon.com/) + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,40 +15,35 @@ * * For more information : contact@centreon.com * - * */ -#include -#include -#include -#include -#include -#include +#include +#include -#include "com/centreon/broker/log_v2.hh" -#include "com/centreon/common/defer.hh" -#include "com/centreon/common/pool.hh" +#include "defer.hh" using system_clock = std::chrono::system_clock; using time_point = system_clock::time_point; using duration = system_clock::duration; -#include "com/centreon/broker/http_client/http_client.hh" +#include "http_client.hh" -using namespace com::centreon::broker; using namespace com::centreon::common; -using namespace com::centreon::broker::http_client; +using namespace com::centreon::common::http; extern std::shared_ptr g_io_context; const asio::ip::tcp::endpoint test_endpoint(asio::ip::make_address("127.0.0.1"), 1234); +static std::shared_ptr logger = + spdlog::stdout_color_mt("http_client_test"); + class http_client_test : public ::testing::Test { public: static void SetUpTestSuite() { srand(time(nullptr)); - log_v2::tcp()->set_level(spdlog::level::debug); + logger->set_level(spdlog::level::debug); }; }; @@ -56,13 +51,16 @@ class connection_ok : public connection_base { unsigned _connect_counter; unsigned _request_counter; + asio::ip::tcp::socket _useless; + public: connection_ok(const std::shared_ptr& io_context, const std::shared_ptr& logger, const http_config::pointer& conf) : connection_base(io_context, logger, conf), _connect_counter(0), - _request_counter(0) {} + _request_counter(0), + _useless(*io_context) {} unsigned get_connect_counter() const { return _connect_counter; } unsigned get_request_counter() const { return _request_counter; } @@ -95,21 +93,27 @@ class connection_ok : public connection_base { } ++_request_counter; } + + void on_accept(connect_callback_type&& callback) override {} + + void answer(const response_ptr& response, + answer_callback_type&& callback) override {} + void receive_request(request_callback_type&& callback) override {} + + asio::ip::tcp::socket& get_socket() override { return _useless; } }; TEST_F(http_client_test, many_request_use_all_connection) { std::vector> conns; + http_config::pointer client_conf = + std::make_shared(test_endpoint, "localhost"); client::pointer clt = - client::load(g_io_context, log_v2::tcp(), - std::make_shared(test_endpoint, "localhost"), - [&conns](const std::shared_ptr& io_context, - const std::shared_ptr& logger, - const http_config::pointer& conf) { - auto dummy_conn = std::make_shared( - io_context, logger, conf); - conns.push_back(dummy_conn); - return dummy_conn; - }); + client::load(g_io_context, logger, client_conf, [&conns, client_conf]() { + auto dummy_conn = + std::make_shared(g_io_context, logger, client_conf); + conns.push_back(dummy_conn); + return dummy_conn; + }); request_ptr request(std::make_shared()); @@ -145,15 +149,12 @@ TEST_F(http_client_test, recycle_connection) { std::vector> conns; auto conf = std::make_shared(test_endpoint, "localhost"); client::pointer clt = - client::load(g_io_context, log_v2::tcp(), conf, - [&conns](const std::shared_ptr& io_context, - const std::shared_ptr& logger, - const http_config::pointer& conf) { - auto dummy_conn = std::make_shared( - io_context, logger, conf); - conns.push_back(dummy_conn); - return dummy_conn; - }); + client::load(g_io_context, logger, conf, [&conns, conf]() { + auto dummy_conn = + std::make_shared(g_io_context, logger, conf); + conns.push_back(dummy_conn); + return dummy_conn; + }); struct sender { request_ptr request = std::make_shared(); @@ -201,6 +202,8 @@ TEST_F(http_client_test, recycle_connection) { *************************************************************************/ class connection_bagot : public connection_base { + asio::ip::tcp::socket _useless; + public: enum fail_stage { no_fail = 0, fail_connect, fail_send }; @@ -211,7 +214,7 @@ class connection_bagot : public connection_base { connection_bagot(const std::shared_ptr& io_context, const std::shared_ptr& logger, const http_config::pointer& conf) - : connection_base(io_context, logger, conf) {} + : connection_base(io_context, logger, conf), _useless(*io_context) {} fail_stage get_fail_stage() const { return _fail_stage; } @@ -262,28 +265,33 @@ class connection_bagot : public connection_base { } } } + + void on_accept(connect_callback_type&& callback) override {} + + void answer(const response_ptr& response, + answer_callback_type&& callback) override {} + void receive_request(request_callback_type&& callback) override {} + + asio::ip::tcp::socket& get_socket() override { return _useless; } }; -class client_test : public http_client::client { +class client_test : public client { public: client_test(const std::shared_ptr& io_context, const std::shared_ptr& logger, - const http_client::http_config::pointer& conf, - http_client::client::connection_creator conn_creator) - : http_client::client(io_context, logger, conf, conn_creator) { + const http_config::pointer& conf, + connection_creator conn_creator) + : client(io_context, logger, conf, conn_creator) { _retry_unit = std::chrono::milliseconds(1); } }; TEST_F(http_client_test, all_handler_called) { + auto client_conf = std::make_shared(test_endpoint, "localhost"); client::pointer clt = std::make_shared( - g_io_context, log_v2::tcp(), - std::make_shared(test_endpoint, "localhost"), - [](const std::shared_ptr& io_context, - const std::shared_ptr& logger, - const http_config::pointer& conf) { - auto dummy_conn = - std::make_shared(io_context, logger, conf); + g_io_context, logger, client_conf, [client_conf]() { + auto dummy_conn = std::make_shared( + g_io_context, logger, client_conf); return dummy_conn; }); @@ -312,8 +320,8 @@ TEST_F(http_client_test, all_handler_called) { return error_handler_cpt + success_handler_cpt == 1000; }); - SPDLOG_LOGGER_INFO(log_v2::tcp(), "success:{}, failed:{}", - success_handler_cpt, error_handler_cpt); + SPDLOG_LOGGER_INFO(logger, "success:{}, failed:{}", success_handler_cpt, + error_handler_cpt); ASSERT_NE(error_handler_cpt, 0); ASSERT_NE(success_handler_cpt, 0); ASSERT_EQ(error_handler_cpt + success_handler_cpt, 1000); @@ -369,13 +377,10 @@ unsigned connection_retry::failed_before_success; TEST_F(http_client_test, retry_until_success) { connection_retry::nb_failed_per_request.clear(); auto conf = std::make_shared(test_endpoint, "localhost"); - client::pointer clt = std::make_shared( - g_io_context, log_v2::tcp(), conf, - [](const std::shared_ptr& io_context, - const std::shared_ptr& logger, - const http_config::pointer& conf) { + client::pointer clt = + std::make_shared(g_io_context, logger, conf, [conf]() { auto dummy_conn = - std::make_shared(io_context, logger, conf); + std::make_shared(g_io_context, logger, conf); return dummy_conn; }); diff --git a/broker/http_client/test/http_connection_test.cc b/common/http/test/http_connection_test.cc similarity index 63% rename from broker/http_client/test/http_connection_test.cc rename to common/http/test/http_connection_test.cc index 9cf757f64d6..fdcb820e9bd 100644 --- a/broker/http_client/test/http_connection_test.cc +++ b/common/http/test/http_connection_test.cc @@ -1,5 +1,5 @@ /** - * Copyright 2022 Centreon (https://www.centreon.com/) + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,24 +15,21 @@ * * For more information : contact@centreon.com * - * */ + #include -#include -#include -#include -#include "com/centreon/broker/log_v2.hh" +#include using system_clock = std::chrono::system_clock; using time_point = system_clock::time_point; using duration = system_clock::duration; -#include "com/centreon/broker/http_client/http_connection.hh" -#include "com/centreon/broker/http_client/https_connection.hh" +#include "http_connection.hh" +#include "http_server.hh" +#include "https_connection.hh" -using namespace com::centreon::broker; -using namespace com::centreon::broker::http_client; +using namespace com::centreon::common::http; namespace beast = boost::beast; extern std::shared_ptr g_io_context; @@ -42,6 +39,9 @@ constexpr unsigned port = 5796; const asio::ip::tcp::endpoint test_endpoint(asio::ip::make_address("127.0.0.1"), port); +const asio::ip::tcp::endpoint listen_endpoint(asio::ip::make_address("0.0.0.0"), + port); + static void create_client_certificate(const std::string& path) { ::remove(path.c_str()); std::ofstream f(path); @@ -93,7 +93,8 @@ static void create_client_certificate(const std::string& path) { "\n"; } -static void load_server_certificate(boost::asio::ssl::context& ctx) { +static void load_server_certificate(boost::asio::ssl::context& ctx, + const http_config::pointer&) { /* The certificate was generated from bash on Ubuntu (OpenSSL 1.1.1f) using: @@ -183,28 +184,40 @@ static void load_server_certificate(boost::asio::ssl::context& ctx) { ctx.use_tmp_dh(boost::asio::buffer(dh.data(), dh.size())); } +static std::shared_ptr logger = + spdlog::stdout_color_mt("http_connection_test"); + /********************************************************************* * http keepalive test *********************************************************************/ class dummy_connection : public connection_base { + asio::ip::tcp::socket _useless; + public: void set_state(int state) { _state = state; } dummy_connection(const std::shared_ptr& io_context, const std::shared_ptr& logger, const http_config::pointer& conf) - : connection_base(io_context, logger, conf) {} + : connection_base(io_context, logger, conf), _useless(*io_context) {} void shutdown() override { _state = e_not_connected; } void connect(connect_callback_type&& callback) override {} void send(request_ptr request, send_callback_type&& callback) override {} + + void on_accept(connect_callback_type&& callback) override{}; + void answer(const response_ptr& response, + answer_callback_type&& callback) override{}; + void receive_request(request_callback_type&& callback) override{}; + + asio::ip::tcp::socket& get_socket() override { return _useless; } }; TEST(http_keepalive_test, ConnectionClose) { dummy_connection conn( - g_io_context, log_v2::tcp(), + g_io_context, logger, std::make_shared(test_endpoint, "localhost")); response_ptr resp(std::make_shared()); resp->keep_alive(false); @@ -215,7 +228,7 @@ TEST(http_keepalive_test, ConnectionClose) { TEST(http_keepalive_test, KeepAliveWithoutTimeout) { auto conf = std::make_shared(test_endpoint, "localhost"); - dummy_connection conn(g_io_context, log_v2::tcp(), conf); + dummy_connection conn(g_io_context, logger, conf); response_ptr resp(std::make_shared()); resp->keep_alive(true); conn.set_state(connection_base::e_idle); @@ -231,7 +244,7 @@ TEST(http_keepalive_test, KeepAliveWithoutTimeout) { TEST(http_keepalive_test, KeepAliveWithTimeout) { auto conf = std::make_shared(test_endpoint, "localhost"); - dummy_connection conn(g_io_context, log_v2::tcp(), conf); + dummy_connection conn(g_io_context, logger, conf); response_ptr resp(std::make_shared()); resp->keep_alive(true); resp->set(beast::http::field::keep_alive, "timeout=5, max=1000"); @@ -250,195 +263,6 @@ TEST(http_keepalive_test, KeepAliveWithTimeout) { * connection test *********************************************************************/ -class session_base : public std::enable_shared_from_this { - protected: - std::shared_ptr _logger; - - public: - using pointer = std::shared_ptr; - - session_base() : _logger(log_v2::tcp()) {} - virtual ~session_base() { SPDLOG_LOGGER_TRACE(_logger, "end session"); } - - virtual void on_receive(const beast::error_code& err, - const request_ptr& request) = 0; - - virtual void send_response(const response_ptr&) = 0; - virtual void shutdown() = 0; - virtual void start() = 0; -}; - -using creator_fct = std::function& /*null if no crypted*/)>; - -static creator_fct creator; - -class http_session : public session_base { - boost::beast::tcp_stream _stream; - boost::beast::flat_buffer _buffer; - - void start_recv() { - request_ptr request(std::make_shared()); - beast::http::async_read( - _stream, _buffer, *request, - [me = shared_from_this(), request](const beast::error_code& err, - std::size_t bytes_received) { - if (err) { - SPDLOG_LOGGER_ERROR(me->_logger, "fail recv {}", err.message()); - me->shutdown(); - } else { - SPDLOG_LOGGER_TRACE(me->_logger, "recv {} bytes", bytes_received); - me->on_receive(err, request); - me->start_recv(); - } - }); - } - - public: - http_session(asio::ip::tcp::socket&& socket, - const std::shared_ptr&) - : _stream(std::move(socket)) {} - - std::shared_ptr shared_from_this() { - return std::static_pointer_cast( - session_base::shared_from_this()); - } - - void start() override { - SPDLOG_LOGGER_TRACE(_logger, "start a session"); - start_recv(); - } - - void send_response(const response_ptr& resp) override { - SPDLOG_LOGGER_TRACE(_logger, "send response"); - beast::http::async_write( - _stream, *resp, - [me = shared_from_this(), resp](const beast::error_code& err, - std::size_t bytes_sent) { - if (err) { - SPDLOG_LOGGER_ERROR(me->_logger, "fail send response"); - me->shutdown(); - } - }); - } - - void shutdown() override { - _stream.socket().shutdown(asio::ip::tcp::socket::shutdown_both); - _stream.close(); - } -}; - -class https_session : public session_base { - beast::ssl_stream _stream; - beast::flat_buffer _buffer; - - void start_recv() { - request_ptr request(std::make_shared()); - beast::http::async_read( - _stream, _buffer, *request, - [me = shared_from_this(), request](const beast::error_code& err, - std::size_t bytes_received) { - if (err) { - SPDLOG_LOGGER_ERROR(me->_logger, "fail recv {}", err.message()); - me->shutdown(); - } else { - SPDLOG_LOGGER_TRACE(me->_logger, "recv {} bytes", bytes_received); - me->on_receive(err, request); - me->start_recv(); - } - }); - } - - public: - https_session(asio::ip::tcp::socket&& socket, - const std::shared_ptr& ctx) - : _stream(std::move(socket), *ctx) {} - - std::shared_ptr shared_from_this() { - return std::static_pointer_cast( - session_base::shared_from_this()); - } - - void start() override { - SPDLOG_LOGGER_TRACE(_logger, "start a session"); - _stream.async_handshake( - asio::ssl::stream_base::server, - [me = shared_from_this()](const beast::error_code& err) { - me->on_handshake(err); - }); - } - - void on_handshake(const beast::error_code& err) { - if (err) { - SPDLOG_LOGGER_TRACE(_logger, "{} fail handshake {}", __FUNCTION__, - err.message()); - shutdown(); - return; - } - SPDLOG_LOGGER_TRACE(_logger, "handshake done"); - start_recv(); - } - - void send_response(const response_ptr& resp) override { - SPDLOG_LOGGER_TRACE(_logger, "send response"); - beast::http::async_write( - _stream, *resp, - [me = shared_from_this(), resp](const beast::error_code& err, - std::size_t bytes_sent) { - if (err) { - SPDLOG_LOGGER_ERROR(me->_logger, "fail send response"); - me->shutdown(); - } - }); - } - - void shutdown() override { - _stream.async_shutdown( - [me = shared_from_this()](const beast::error_code&) {}); - } -}; - -class listener : public std::enable_shared_from_this { - protected: - std::shared_ptr _ssl_context; - asio::ip::tcp::acceptor _acceptor; - std::shared_ptr _logger; - - void do_accept() { - // The new connection gets its own strand - _acceptor.async_accept( - asio::make_strand(*g_io_context), - beast::bind_front_handler(&listener::on_accept, shared_from_this())); - } - - void on_accept(const beast::error_code& ec, asio::ip::tcp::socket socket) { - if (ec) { - SPDLOG_LOGGER_ERROR(_logger, "fail accept"); - return; - } - SPDLOG_LOGGER_DEBUG(_logger, "accept a connection"); - auto session = creator(std::move(socket), _ssl_context); - session->start(); - do_accept(); - } - - public: - using pointer = std::shared_ptr; - - listener(unsigned port) - : _ssl_context(std::make_shared( - asio::ssl::context::sslv23_server)), - _acceptor(*g_io_context, test_endpoint, true), - _logger(log_v2::tcp()) { - load_server_certificate(*_ssl_context); - } - - void start() { do_accept(); } - - void shutdown() { _acceptor.close(); } -}; - const char* client_cert_path = "/tmp/client_test.cert"; /** @@ -446,20 +270,11 @@ const char* client_cert_path = "/tmp/client_test.cert"; * */ class http_test : public ::testing::TestWithParam { - protected: - static listener::pointer _listener; - public: static void SetUpTestSuite() { create_client_certificate(client_cert_path); - log_v2::tcp()->set_level(spdlog::level::info); - _listener = std::make_shared(port); - _listener->start(); + logger->set_level(spdlog::level::debug); }; - static void TearDownTestSuite() { - _listener->shutdown(); - _listener.reset(); - } void SetUp() override {} void TearDown() override {} @@ -476,54 +291,78 @@ class http_test : public ::testing::TestWithParam { return std::make_shared(test_endpoint, "localhost", false); } } -}; + http_config::pointer create_server_conf() { + if (GetParam()) { + return std::make_shared( + listen_endpoint, "localhost", true, std::chrono::seconds(10), + std::chrono::seconds(10), std::chrono::seconds(10), 30, + std::chrono::seconds(10), 5, std::chrono::hours(1), 10, + asio::ssl::context_base::sslv23_server); -listener::pointer http_test::_listener; + } else { + return std::make_shared( + listen_endpoint, "localhost", false, std::chrono::seconds(10), + std::chrono::seconds(10), std::chrono::seconds(10), 30, + std::chrono::seconds(10), 5, std::chrono::hours(1), 10); + } + } +}; -// simple exchange with no keepalive template class answer_no_keep_alive : public base_class { public: - answer_no_keep_alive(asio::ip::tcp::socket&& socket, - const std::shared_ptr& ssl_context) - : base_class(std::move(socket), ssl_context) {} - - void on_receive(const beast::error_code& err, const request_ptr& request) { - if (!err) { - ASSERT_EQ(request->body(), "hello server"); - response_ptr resp(std::make_shared(beast::http::status::ok, - request->version())); - resp->keep_alive(false); - resp->body() = "hello client"; - resp->content_length(resp->body().length()); - base_class::send_response(resp); - } + using my_type = answer_no_keep_alive; + + answer_no_keep_alive(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_initializer) + : base_class(io_context, logger, conf, ssl_initializer) {} + + void on_accept() override { + base_class::on_accept([me = base_class::shared_from_this()]( + const boost::beast::error_code ec, + const std::string&) { + ASSERT_FALSE(ec); + me->receive_request([me](const boost::beast::error_code ec, + const std::string&, + const std::shared_ptr& request) { + ASSERT_FALSE(ec); + ASSERT_EQ(request->body(), "hello server"); + response_ptr resp(std::make_shared( + beast::http::status::ok, request->version())); + resp->keep_alive(false); + resp->body() = "hello client"; + resp->content_length(resp->body().length()); + me->answer(resp, [](const boost::beast::error_code ec, + const std::string&) { ASSERT_FALSE(ec); }); + }); + }); } }; TEST_P(http_test, connect_send_answer_without_keepalive) { std::shared_ptr conn; - http_config::pointer conf = create_conf(); + http_config::pointer client_conf = create_conf(); + http_config::pointer server_conf = create_server_conf(); + + connection_creator server_creator; if (GetParam()) { // crypted - creator = [](asio::ip::tcp::socket&& socket, - const std::shared_ptr& ctx) - -> session_base::pointer { - return std::make_shared>( - std::move(socket), ctx); + server_creator = [server_conf]() { + return std::make_shared>( + g_io_context, logger, server_conf, load_server_certificate); }; } else { - creator = [](asio::ip::tcp::socket&& socket, - const std::shared_ptr& ctx) - -> session_base::pointer { - return std::make_shared>( - std::move(socket), ctx); + server_creator = [server_conf]() { + return std::make_shared>( + g_io_context, logger, server_conf, nullptr); }; } auto client = GetParam() - ? https_connection::load(g_io_context, log_v2::tcp(), conf) - : http_connection::load(g_io_context, log_v2::tcp(), conf); + ? https_connection::load(g_io_context, logger, client_conf) + : http_connection::load(g_io_context, logger, client_conf); request_ptr request(std::make_shared()); request->method(beast::http::verb::put); request->target("/"); @@ -533,6 +372,9 @@ TEST_P(http_test, connect_send_answer_without_keepalive) { auto f(p.get_future()); time_point send_begin = system_clock::now(); + auto serv = server::load(g_io_context, logger, server_conf, + std::move(server_creator)); + client->connect([&p, client, request](const beast::error_code& err, const std::string& detail) { if (err) { @@ -548,13 +390,17 @@ TEST_P(http_test, connect_send_answer_without_keepalive) { auto completion = f.get(); time_point send_end = system_clock::now(); - ASSERT_LT((send_end - send_begin), std::chrono::milliseconds(100)); + ASSERT_LT(std::chrono::duration_cast(send_end - + send_begin), + std::chrono::milliseconds(200)); ASSERT_FALSE(std::get<0>(completion)); ASSERT_TRUE(std::get<1>(completion).empty()); ASSERT_EQ(std::get<2>(completion)->body(), "hello client"); ASSERT_EQ(std::get<2>(completion)->keep_alive(), false); std::this_thread::sleep_for(std::chrono::seconds(1)); ASSERT_EQ(client->get_state(), connection_base::e_not_connected); + + serv->shutdown(); } // simple exchange with keepalive @@ -563,46 +409,77 @@ class answer_keep_alive : public base_class { unsigned _counter; public: - answer_keep_alive(asio::ip::tcp::socket&& socket, - const std::shared_ptr& ssl_context) - : base_class(std::move(socket), ssl_context), _counter(0) {} - - void on_receive(const beast::error_code& err, const request_ptr& request) { - if (!err) { - ASSERT_EQ(request->body(), "hello server"); - response_ptr resp(std::make_shared(beast::http::status::ok, - request->version())); - resp->keep_alive(true); - resp->body() = fmt::format("hello client {}", _counter++); - resp->content_length(resp->body().length()); - base_class::send_response(resp); - } + using my_type = answer_keep_alive; + + answer_keep_alive(const std::shared_ptr& io_context, + const std::shared_ptr& logger, + const http_config::pointer& conf, + const ssl_ctx_initializer& ssl_initializer) + : base_class(io_context, logger, conf, ssl_initializer), _counter(0) {} + + void on_accept() override { + base_class::on_accept([me = base_class::shared_from_this()]( + const boost::beast::error_code ec, + const std::string&) { + ASSERT_FALSE(ec); + me->receive_request([me](const boost::beast::error_code ec, + const std::string&, + const std::shared_ptr& request) { + ASSERT_FALSE(ec); + ASSERT_EQ(request->body(), "hello server"); + SPDLOG_LOGGER_DEBUG(logger, "request receiver => answer"); + std::static_pointer_cast(me)->answer(request); + }); + }); + } + + void answer(const std::shared_ptr& request) { + response_ptr resp(std::make_shared(beast::http::status::ok, + request->version())); + resp->keep_alive(true); + resp->body() = fmt::format("hello client {}", _counter++); + resp->content_length(resp->body().length()); + SPDLOG_LOGGER_DEBUG(logger, "answer to client"); + base_class::answer( + resp, [me = base_class::shared_from_this()]( + const boost::beast::error_code ec, const std::string&) { + ASSERT_FALSE(ec); + me->receive_request( + [me](const boost::beast::error_code ec, const std::string&, + const std::shared_ptr& request) { + if (ec) { + return; + } + ASSERT_EQ(request->body(), "hello server"); + SPDLOG_LOGGER_DEBUG(logger, "request receiver => answer"); + std::static_pointer_cast(me)->answer(request); + }); + }); } }; TEST_P(http_test, connect_send_answer_with_keepalive) { std::shared_ptr conn; - http_config::pointer conf = create_conf(); + http_config::pointer client_conf = create_conf(); + http_config::pointer server_conf = create_server_conf(); + + connection_creator server_creator; if (GetParam()) { // crypted - creator = [](asio::ip::tcp::socket&& socket, - const std::shared_ptr& ctx) - -> session_base::pointer { - return std::make_shared>( - std::move(socket), ctx); + server_creator = [server_conf]() { + return std::make_shared>( + g_io_context, logger, server_conf, load_server_certificate); }; } else { - creator = [](asio::ip::tcp::socket&& socket, - const std::shared_ptr& ctx) - -> session_base::pointer { - return std::make_shared>( - std::move(socket), ctx); + server_creator = [server_conf]() { + return std::make_shared>( + g_io_context, logger, server_conf, nullptr); }; } auto client = GetParam() - ? https_connection::load(g_io_context, log_v2::tcp(), conf) - : http_connection::load(g_io_context, log_v2::tcp(), conf); + ? https_connection::load(g_io_context, logger, client_conf) + : http_connection::load(g_io_context, logger, client_conf); request_ptr request(std::make_shared()); request->method(beast::http::verb::put); request->target("/"); @@ -612,6 +489,9 @@ TEST_P(http_test, connect_send_answer_with_keepalive) { auto f(p.get_future()); time_point send_begin = system_clock::now(); + auto serv = server::load(g_io_context, logger, server_conf, + std::move(server_creator)); + client->connect([&p, client, request](const beast::error_code& err, const std::string& detail) { if (err) { @@ -627,7 +507,9 @@ TEST_P(http_test, connect_send_answer_with_keepalive) { auto completion = f.get(); time_point send_end = system_clock::now(); - ASSERT_LT((send_end - send_begin), std::chrono::milliseconds(100)); + ASSERT_LT(std::chrono::duration_cast(send_end - + send_begin), + std::chrono::milliseconds(200)); ASSERT_FALSE(std::get<0>(completion)); ASSERT_TRUE(std::get<1>(completion).empty()); ASSERT_EQ(std::get<2>(completion)->body(), "hello client 0"); @@ -650,6 +532,8 @@ TEST_P(http_test, connect_send_answer_with_keepalive) { ASSERT_EQ(std::get<2>(completion2)->keep_alive(), true); std::this_thread::sleep_for(std::chrono::milliseconds(10)); ASSERT_EQ(client->get_state(), connection_base::e_idle); + + serv->shutdown(); } INSTANTIATE_TEST_SUITE_P(http_connection, diff --git a/common/inc/com/centreon/common/hex_dump.hh b/common/inc/com/centreon/common/hex_dump.hh index 1782c8f6ac8..73b570a8605 100644 --- a/common/inc/com/centreon/common/hex_dump.hh +++ b/common/inc/com/centreon/common/hex_dump.hh @@ -1,11 +1,11 @@ /** - * Copyright 2023 Centreon + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,15 +14,16 @@ * limitations under the License. * * For more information : contact@centreon.com + * */ #ifndef CCCM_HEX_DUMP_HH #define CCCM_HEX_DUMP_HH - namespace com::centreon::common { -std::string hex_dump(const unsigned char* buffer, size_t buff_len, +std::string hex_dump(const unsigned char* buffer, + size_t buff_len, uint32_t nb_char_per_line); inline std::string hex_dump(const std::string& buffer, diff --git a/common/precomp_inc/precomp.hh b/common/precomp_inc/precomp.hh index 6af5512cfee..07853c4edab 100644 --- a/common/precomp_inc/precomp.hh +++ b/common/precomp_inc/precomp.hh @@ -1,5 +1,5 @@ -/* - * Copyright 2022 Centreon (https://www.centreon.com/) +/** + * Copyright 2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,9 @@ #include "com/centreon/exceptions/msg_fmt.hh" +using system_clock = std::chrono::system_clock; +using time_point = system_clock::time_point; +using duration = system_clock::duration; namespace asio = boost::asio; diff --git a/common/src/process_stat.cc b/common/src/process_stat.cc index 7ae145d0d94..fa784d68ffa 100644 --- a/common/src/process_stat.cc +++ b/common/src/process_stat.cc @@ -1,21 +1,21 @@ /** -* Copyright 2023 Centreon -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* For more information : contact@centreon.com -*/ - + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #include #include diff --git a/common/test/CMakeLists.txt b/common/test/CMakeLists.txt index 18fafc9fbb5..08b61a4a9be 100644 --- a/common/test/CMakeLists.txt +++ b/common/test/CMakeLists.txt @@ -16,26 +16,21 @@ # For more information : contact@centreon.com # -add_executable(ut_common - process_stat_test.cc - hex_dump_test.cc - node_allocator_test.cc - rapidjson_helper_test.cc -) -add_test(NAME tests COMMAND ut_common) +add_executable(ut_common + process_stat_test.cc + hex_dump_test.cc + node_allocator_test.cc + rapidjson_helper_test.cc + test_main.cc + ${TESTS_SOURCES}) -set_target_properties( - ut_common - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests - RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/tests - RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/tests - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/tests - RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/tests) +add_test(NAME tests COMMAND ut_common) target_link_libraries(ut_common PRIVATE centreon_common + centreon_http crypto ssl GTest::gtest @@ -48,9 +43,17 @@ target_link_libraries(ut_common PRIVATE absl::bits fmt::fmt pthread) -add_dependencies(ut_common centreon_common) +add_dependencies(ut_common centreon_common centreon_http) set_property(TARGET ut_common PROPERTY POSITION_INDEPENDENT_CODE ON) target_precompile_headers(ut_common PRIVATE ${PROJECT_SOURCE_DIR}/precomp_inc/precomp.hh) +set_target_properties( + ut_common + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/tests) + diff --git a/common/test/process_stat_test.cc b/common/test/process_stat_test.cc index 79cd1bc4a9f..401ccf777c5 100644 --- a/common/test/process_stat_test.cc +++ b/common/test/process_stat_test.cc @@ -1,20 +1,21 @@ /** -* Copyright 2023 Centreon -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* For more information : contact@centreon.com -*/ + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ #include diff --git a/common/test/test_main.cc b/common/test/test_main.cc new file mode 100644 index 00000000000..995392ef7a4 --- /dev/null +++ b/common/test/test_main.cc @@ -0,0 +1,62 @@ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "pool.hh" + +std::shared_ptr g_io_context( + std::make_shared()); + +class CentreonEngineEnvironment : public testing::Environment { + public: + void SetUp() override { + setenv("TZ", ":Europe/Paris", 1); + return; + } + + void TearDown() override { return; } +}; + +std::shared_ptr pool_logger = + std::make_shared("pool_logger"); + +/** + * Tester entry point. + * + * @param[in] argc Argument count. + * @param[in] argv Argument values. + * + * @return 0 on success, any other value on failure. + */ +int main(int argc, char* argv[]) { + // GTest initialization. + testing::InitGoogleTest(&argc, argv); + + // Set specific environment. + testing::AddGlobalTestEnvironment(new CentreonEngineEnvironment()); + + com::centreon::common::pool::load(g_io_context, pool_logger); + com::centreon::common::pool::set_pool_size(0); + // Run all tests. + int ret = RUN_ALL_TESTS(); + g_io_context->stop(); + com::centreon::common::pool::unload(); + spdlog::shutdown(); + return ret; +}