refactor: Refactor main (#1555)

For #442.
This commit is contained in:
Sergey Kuznetsov
2024-08-01 10:53:17 +01:00
committed by GitHub
parent fb90fb27ae
commit 2fef03d766
36 changed files with 920 additions and 376 deletions

View File

@@ -149,6 +149,13 @@ jobs:
name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}_${{ steps.conan.outputs.conan_profile }}
path: build/clio_*tests
- name: Upload test data
if: ${{ !matrix.code_coverage }}
uses: actions/upload-artifact@v4
with:
name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}_${{ steps.conan.outputs.conan_profile }}
path: build/tests/unit/test_data
- name: Save cache
uses: ./.github/actions/save_cache
with:
@@ -211,6 +218,12 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}_${{ matrix.conan_profile }}
- uses: actions/download-artifact@v4
with:
name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}_${{ matrix.conan_profile }}
path: tests/unit/test_data
- name: Run clio_tests
run: |
chmod +x ./clio_tests

View File

@@ -71,6 +71,12 @@ jobs:
name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}
path: build/clio_*tests
- name: Upload test data
uses: actions/upload-artifact@v4
with:
name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}
path: build/tests/unit/test_data
- name: Compress clio_server
shell: bash
run: |
@@ -124,6 +130,11 @@ jobs:
with:
name: clio_tests_${{ runner.os }}_${{ matrix.build_type }}
- uses: actions/download-artifact@v4
with:
name: clio_test_data_${{ runner.os }}_${{ matrix.build_type }}
path: tests/unit/test_data
- name: Run clio_tests
run: |
chmod +x ./clio_tests

2
.gitignore vendored
View File

@@ -8,4 +8,4 @@
.DS_Store
CMakeUserPresets.json
config.json
src/main/impl/Build.cpp
src/util/build/Build.cpp

View File

@@ -17,11 +17,12 @@
*/
//==============================================================================
#include "main/Build.hpp"
#include "util/build/Build.hpp"
#include <string>
namespace Build {
namespace util::build {
static constexpr char versionString[] = "@CLIO_VERSION@";
std::string const&
@@ -38,4 +39,4 @@ getClioFullVersionString()
return value;
}
} // namespace Build
} // namespace util::build

View File

@@ -45,4 +45,4 @@ endif ()
message(STATUS "Build version: ${CLIO_VERSION}")
configure_file(${CMAKE_CURRENT_LIST_DIR}/Build.cpp.in ${CMAKE_CURRENT_LIST_DIR}/../src/main/impl/Build.cpp)
configure_file(${CMAKE_CURRENT_LIST_DIR}/Build.cpp.in ${CMAKE_CURRENT_LIST_DIR}/../src/util/build/Build.cpp)

View File

@@ -4,4 +4,5 @@ add_subdirectory(etl)
add_subdirectory(feed)
add_subdirectory(rpc)
add_subdirectory(web)
add_subdirectory(app)
add_subdirectory(main)

4
src/app/CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
add_library(clio_app)
target_sources(clio_app PRIVATE CliArgs.cpp ClioApplication.cpp)
target_link_libraries(clio_app PUBLIC clio_etl clio_feed clio_web clio_rpc)

70
src/app/CliArgs.cpp Normal file
View File

@@ -0,0 +1,70 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "app/CliArgs.hpp"
#include "util/build/Build.hpp"
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/positional_options.hpp>
#include <boost/program_options/value_semantic.hpp>
#include <boost/program_options/variables_map.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
#include <utility>
namespace app {
CliArgs::Action
CliArgs::parse(int argc, char const* argv[])
{
namespace po = boost::program_options;
// clang-format off
po::options_description description("Options");
description.add_options()
("help,h", "print help message and exit")
("version,v", "print version and exit")
("conf,c", po::value<std::string>()->default_value(defaultConfigPath), "configuration file")
;
// clang-format on
po::positional_options_description positional;
positional.add("conf", 1);
po::variables_map parsed;
po::store(po::command_line_parser(argc, argv).options(description).positional(positional).run(), parsed);
po::notify(parsed);
if (parsed.count("help") != 0u) {
std::cout << "Clio server " << util::build::getClioFullVersionString() << "\n\n" << description;
return Action{Action::Exit{EXIT_SUCCESS}};
}
if (parsed.count("version") != 0u) {
std::cout << util::build::getClioFullVersionString() << '\n';
return Action{Action::Exit{EXIT_SUCCESS}};
}
auto configPath = parsed["conf"].as<std::string>();
return Action{Action::Run{std::move(configPath)}};
}
} // namespace app

96
src/app/CliArgs.hpp Normal file
View File

@@ -0,0 +1,96 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "util/OverloadSet.hpp"
#include <string>
#include <variant>
namespace app {
/**
* @brief Parsed command line arguments representation.
*/
class CliArgs {
public:
/**
* @brief Default configuration path.
*/
static constexpr char defaultConfigPath[] = "/etc/opt/clio/config.json";
/**
* @brief An action parsed from the command line.
*/
class Action {
public:
/** @brief Run action. */
struct Run {
/** @brief Configuration file path. */
std::string configPath;
};
/** @brief Exit action. */
struct Exit {
/** @brief Exit code. */
int exitCode;
};
/**
* @brief Construct an action from a Run.
*
* @param action Run action.
*/
template <typename ActionType>
requires std::is_same_v<ActionType, Run> or std::is_same_v<ActionType, Exit>
explicit Action(ActionType&& action) : action_(std::forward<ActionType>(action))
{
}
/**
* @brief Apply a function to the action.
*
* @tparam Processors Action processors types. Must be callable with the action type and return int.
* @param processors Action processors.
* @return Exit code.
*/
template <typename... Processors>
int
apply(Processors&&... processors) const
{
return std::visit(util::OverloadSet{std::forward<Processors>(processors)...}, action_);
}
private:
std::variant<Run, Exit> action_;
};
/**
* @brief Parse command line arguments.
*
* @param argc Number of arguments.
* @param argv Array of arguments.
* @return Parsed command line arguments.
*/
static Action
parse(int argc, char const* argv[]);
};
} // namespace app

142
src/app/ClioApplication.cpp Normal file
View File

@@ -0,0 +1,142 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "app/ClioApplication.hpp"
#include "data/AmendmentCenter.hpp"
#include "data/BackendFactory.hpp"
#include "etl/ETLService.hpp"
#include "etl/LoadBalancer.hpp"
#include "etl/NetworkValidatedLedgers.hpp"
#include "feed/SubscriptionManager.hpp"
#include "rpc/Counters.hpp"
#include "rpc/RPCEngine.hpp"
#include "rpc/WorkQueue.hpp"
#include "rpc/common/impl/HandlerProvider.hpp"
#include "util/build/Build.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Prometheus.hpp"
#include "web/DOSGuard.hpp"
#include "web/IntervalSweepHandler.hpp"
#include "web/RPCServerHandler.hpp"
#include "web/Server.hpp"
#include "web/WhitelistHandler.hpp"
#include <boost/asio/io_context.hpp>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <thread>
#include <vector>
namespace app {
namespace {
/**
* @brief Start context threads
*
* @param ioc Context
* @param numThreads Number of worker threads to start
*/
void
start(boost::asio::io_context& ioc, std::uint32_t numThreads)
{
std::vector<std::thread> v;
v.reserve(numThreads - 1);
for (auto i = numThreads - 1; i > 0; --i)
v.emplace_back([&ioc] { ioc.run(); });
ioc.run();
for (auto& t : v)
t.join();
}
} // namespace
ClioApplication::ClioApplication(util::Config const& config) : config_(config), signalsHandler_{config_}
{
LOG(util::LogService::info()) << "Clio version: " << util::build::getClioFullVersionString();
PrometheusService::init(config);
}
int
ClioApplication::run()
{
auto const threads = config_.valueOr("io_threads", 2);
if (threads <= 0) {
LOG(util::LogService::fatal()) << "io_threads is less than 1";
return EXIT_FAILURE;
}
LOG(util::LogService::info()) << "Number of io threads = " << threads;
// IO context to handle all incoming requests, as well as other things.
// This is not the only io context in the application.
boost::asio::io_context ioc{threads};
// Rate limiter, to prevent abuse
auto sweepHandler = web::IntervalSweepHandler{config_, ioc};
auto whitelistHandler = web::WhitelistHandler{config_};
auto dosGuard = web::DOSGuard{config_, whitelistHandler, sweepHandler};
// Interface to the database
auto backend = data::make_Backend(config_);
// Manages clients subscribed to streams
auto subscriptionsRunner = feed::SubscriptionManagerRunner(config_, backend);
auto const subscriptions = subscriptionsRunner.getManager();
// Tracks which ledgers have been validated by the network
auto ledgers = etl::NetworkValidatedLedgers::make_ValidatedLedgers();
// Handles the connection to one or more rippled nodes.
// ETL uses the balancer to extract data.
// The server uses the balancer to forward RPCs to a rippled node.
// The balancer itself publishes to streams (transactions_proposed and accounts_proposed)
auto balancer = etl::LoadBalancer::make_LoadBalancer(config_, ioc, backend, subscriptions, ledgers);
// ETL is responsible for writing and publishing to streams. In read-only mode, ETL only publishes
auto etl = etl::ETLService::make_ETLService(config_, ioc, backend, subscriptions, balancer, ledgers);
auto workQueue = rpc::WorkQueue::make_WorkQueue(config_);
auto counters = rpc::Counters::make_Counters(workQueue);
auto const amendmentCenter = std::make_shared<data::AmendmentCenter const>(backend);
auto const handlerProvider = std::make_shared<rpc::impl::ProductionHandlerProvider const>(
config_, backend, subscriptions, balancer, etl, amendmentCenter, counters
);
auto const rpcEngine =
rpc::RPCEngine::make_RPCEngine(backend, balancer, dosGuard, workQueue, counters, handlerProvider);
// Init the web server
auto handler =
std::make_shared<web::RPCServerHandler<rpc::RPCEngine, etl::ETLService>>(config_, backend, rpcEngine, etl);
auto const httpServer = web::make_HttpServer(config_, ioc, dosGuard, handler);
// Blocks until stopped.
// When stopped, shared_ptrs fall out of scope
// Calls destructors on all resources, and destructs in order
start(ioc, threads);
return EXIT_SUCCESS;
}
} // namespace app

View File

@@ -0,0 +1,51 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include "util/SignalsHandler.hpp"
#include "util/config//Config.hpp"
namespace app {
/**
* @brief The main application class
*/
class ClioApplication {
util::Config const& config_;
util::SignalsHandler signalsHandler_;
public:
/**
* @brief Construct a new ClioApplication object
*
* @param config The configuration of the application
*/
ClioApplication(util::Config const& config);
/**
* @brief Run the application
*
* @return exit code
*/
int
run();
};
} // namespace app

View File

@@ -21,6 +21,7 @@
#include "data/cassandra/impl/ManagedObject.hpp"
#include "data/cassandra/impl/SslContext.hpp"
#include "util/OverloadSet.hpp"
#include "util/log/Logger.hpp"
#include <cassandra.h>
@@ -31,16 +32,9 @@
#include <variant>
namespace {
constexpr auto clusterDeleter = [](CassCluster* ptr) { cass_cluster_free(ptr); };
template <typename... Ts>
struct overloadSet : Ts... {
using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20, but clang be clang)
template <typename... Ts>
overloadSet(Ts...) -> overloadSet<Ts...>;
}; // namespace
namespace data::cassandra::impl {
@@ -90,7 +84,7 @@ void
Cluster::setupConnection(Settings const& settings)
{
std::visit(
overloadSet{
util::OverloadSet{
[this](Settings::ContactPoints const& points) { setupContactPoints(points); },
[this](Settings::SecureConnectionBundle const& bundle) { setupSecureBundle(bundle); }
},

View File

@@ -1,14 +1,7 @@
add_library(clio)
target_sources(clio PRIVATE impl/Build.cpp)
target_link_libraries(clio PUBLIC clio_etl clio_feed clio_web clio_rpc)
target_compile_features(clio PUBLIC cxx_std_23)
# Clio server
add_executable(clio_server)
target_sources(clio_server PRIVATE Main.cpp)
target_link_libraries(clio_server PRIVATE clio)
target_link_libraries(clio_server PRIVATE clio_app)
if (static)
if (san)

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2022-2023, the clio developers.
Copyright (c) 2022-2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -17,234 +17,44 @@
*/
//==============================================================================
#include "data/AmendmentCenter.hpp"
#include "data/BackendFactory.hpp"
#include "etl/ETLService.hpp"
#include "etl/NetworkValidatedLedgers.hpp"
#include "feed/SubscriptionManager.hpp"
#include "main/Build.hpp"
#include "rpc/Counters.hpp"
#include "rpc/RPCEngine.hpp"
#include "rpc/WorkQueue.hpp"
#include "app/CliArgs.hpp"
#include "app/ClioApplication.hpp"
#include "rpc/common/impl/HandlerProvider.hpp"
#include "util/SignalsHandler.hpp"
#include "util/TerminationHandler.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Prometheus.hpp"
#include "web/DOSGuard.hpp"
#include "web/IntervalSweepHandler.hpp"
#include "web/RPCServerHandler.hpp"
#include "web/Server.hpp"
#include <boost/asio/buffer.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/positional_options.hpp>
#include <boost/program_options/value_semantic.hpp>
#include <boost/program_options/variables_map.hpp>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <fstream>
#include <functional>
#include <ios>
#include <iostream>
#include <memory>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
using namespace util;
using namespace boost::asio;
namespace po = boost::program_options;
/**
* @brief Parse command line and return path to configuration file
*
* @param argc
* @param argv
* @return Path to configuration file
*/
std::string
parseCli(int argc, char* argv[])
{
static constexpr char defaultConfigPath[] = "/etc/opt/clio/config.json";
// clang-format off
po::options_description description("Options");
description.add_options()
("help,h", "print help message and exit")
("version,v", "print version and exit")
("conf,c", po::value<std::string>()->default_value(defaultConfigPath), "configuration file")
;
// clang-format on
po::positional_options_description positional;
positional.add("conf", 1);
po::variables_map parsed;
po::store(po::command_line_parser(argc, argv).options(description).positional(positional).run(), parsed);
po::notify(parsed);
if (parsed.count("version") != 0u) {
std::cout << Build::getClioFullVersionString() << '\n';
std::exit(EXIT_SUCCESS);
}
if (parsed.count("help") != 0u) {
std::cout << "Clio server " << Build::getClioFullVersionString() << "\n\n" << description;
std::exit(EXIT_SUCCESS);
}
return parsed["conf"].as<std::string>();
}
/**
* @brief Parse certificates from configuration file
*
* @param config The configuration
* @return SSL context if certificates were parsed
*/
std::optional<ssl::context>
parseCerts(Config const& config)
{
if (!config.contains("ssl_cert_file") || !config.contains("ssl_key_file"))
return {};
auto certFilename = config.value<std::string>("ssl_cert_file");
auto keyFilename = config.value<std::string>("ssl_key_file");
std::ifstream const readCert(certFilename, std::ios::in | std::ios::binary);
if (!readCert)
return {};
std::stringstream contents;
contents << readCert.rdbuf();
std::string cert = contents.str();
std::ifstream readKey(keyFilename, std::ios::in | std::ios::binary);
if (!readKey)
return {};
contents.str("");
contents << readKey.rdbuf();
readKey.close();
std::string key = contents.str();
ssl::context ctx{ssl::context::tls_server};
ctx.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
ctx.use_certificate_chain(buffer(cert.data(), cert.size()));
ctx.use_private_key(buffer(key.data(), key.size()), ssl::context::file_format::pem);
return ctx;
}
/**
* @brief Start context threads
*
* @param ioc Context
* @param numThreads Number of worker threads to start
*/
void
start(io_context& ioc, std::uint32_t numThreads)
{
std::vector<std::thread> v;
v.reserve(numThreads - 1);
for (auto i = numThreads - 1; i > 0; --i)
v.emplace_back([&ioc] { ioc.run(); });
ioc.run();
for (auto& t : v)
t.join();
}
int
main(int argc, char* argv[])
main(int argc, char const* argv[])
try {
util::setTerminationHandler();
auto const configPath = parseCli(argc, argv);
auto const config = ConfigReader::open(configPath);
auto const action = app::CliArgs::parse(argc, argv);
return action.apply(
[](app::CliArgs::Action::Exit const& exit) { return exit.exitCode; },
[](app::CliArgs::Action::Run const& run) {
auto const config = util::ConfigReader::open(run.configPath);
if (!config) {
std::cerr << "Couldnt parse config '" << configPath << "'." << std::endl;
std::cerr << "Couldnt parse config '" << run.configPath << "'." << std::endl;
return EXIT_FAILURE;
}
util::SignalsHandler signalsHandler{config};
LogService::init(config);
LOG(LogService::info()) << "Clio version: " << Build::getClioFullVersionString();
PrometheusService::init(config);
auto const threads = config.valueOr("io_threads", 2);
if (threads <= 0) {
LOG(LogService::fatal()) << "io_threads is less than 1";
return EXIT_FAILURE;
util::LogService::init(config);
app::ClioApplication clio{config};
return clio.run();
}
LOG(LogService::info()) << "Number of io threads = " << threads;
// IO context to handle all incoming requests, as well as other things.
// This is not the only io context in the application.
io_context ioc{threads};
// Rate limiter, to prevent abuse
auto sweepHandler = web::IntervalSweepHandler{config, ioc};
auto whitelistHandler = web::WhitelistHandler{config};
auto dosGuard = web::DOSGuard{config, whitelistHandler, sweepHandler};
// Interface to the database
auto backend = data::make_Backend(config);
// Manages clients subscribed to streams
auto subscriptionsRunner = feed::SubscriptionManagerRunner(config, backend);
auto const subscriptions = subscriptionsRunner.getManager();
// Tracks which ledgers have been validated by the network
auto ledgers = etl::NetworkValidatedLedgers::make_ValidatedLedgers();
// Handles the connection to one or more rippled nodes.
// ETL uses the balancer to extract data.
// The server uses the balancer to forward RPCs to a rippled node.
// The balancer itself publishes to streams (transactions_proposed and accounts_proposed)
auto balancer = etl::LoadBalancer::make_LoadBalancer(config, ioc, backend, subscriptions, ledgers);
// ETL is responsible for writing and publishing to streams. In read-only mode, ETL only publishes
auto etl = etl::ETLService::make_ETLService(config, ioc, backend, subscriptions, balancer, ledgers);
auto workQueue = rpc::WorkQueue::make_WorkQueue(config);
auto counters = rpc::Counters::make_Counters(workQueue);
auto const amendmentCenter = std::make_shared<data::AmendmentCenter const>(backend);
auto const handlerProvider = std::make_shared<rpc::impl::ProductionHandlerProvider const>(
config, backend, subscriptions, balancer, etl, amendmentCenter, counters
);
auto const rpcEngine =
rpc::RPCEngine::make_RPCEngine(backend, balancer, dosGuard, workQueue, counters, handlerProvider);
// Init the web server
auto handler =
std::make_shared<web::RPCServerHandler<rpc::RPCEngine, etl::ETLService>>(config, backend, rpcEngine, etl);
auto ctx = parseCerts(config);
auto const ctxRef = ctx ? std::optional<std::reference_wrapper<ssl::context>>{ctx.value()} : std::nullopt;
auto const httpServer = web::make_HttpServer(config, ioc, ctxRef, dosGuard, handler);
// Blocks until stopped.
// When stopped, shared_ptrs fall out of scope
// Calls destructors on all resources, and destructs in order
start(ioc, threads);
return EXIT_SUCCESS;
} catch (std::exception const& e) {
LOG(LogService::fatal()) << "Exit on exception: " << e.what();
LOG(util::LogService::fatal()) << "Exit on exception: " << e.what();
return EXIT_FAILURE;
} catch (...) {
LOG(LogService::fatal()) << "Exit on exception: unknown";
LOG(util::LogService::fatal()) << "Exit on exception: unknown";
return EXIT_FAILURE;
}

View File

@@ -20,6 +20,7 @@
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "util/OverloadSet.hpp"
#include <boost/json/object.hpp>
#include <xrpl/protocol/ErrorCodes.h>
@@ -36,17 +37,6 @@
using namespace std;
namespace {
template <typename... Ts>
struct overloadSet : Ts... {
using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20, but clang be clang)
template <typename... Ts>
overloadSet(Ts...) -> overloadSet<Ts...>;
} // namespace
namespace rpc {
WarningInfo const&
@@ -149,7 +139,7 @@ makeError(Status const& status)
auto wrapOptional = [](string_view const& str) { return str.empty() ? nullopt : make_optional(str); };
auto res = visit(
overloadSet{
util::OverloadSet{
[&status, &wrapOptional](RippledError err) {
if (err == ripple::rpcUNKNOWN)
return boost::json::object{{"error", status.message}, {"type", "response"}, {"status", "error"}};

View File

@@ -22,11 +22,11 @@
#include "data/BackendInterface.hpp"
#include "data/DBHelpers.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "main/Build.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "util/build/Build.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
@@ -124,7 +124,7 @@ public:
uint32_t loadFactor = 1u;
std::chrono::time_point<std::chrono::system_clock> time = std::chrono::system_clock::now();
std::chrono::seconds uptime = {};
std::string clioVersion = Build::getClioVersionString();
std::string clioVersion = util::build::getClioVersionString();
std::string xrplVersion = ripple::BuildInfo::getVersionString();
std::optional<boost::json::object> rippledInfo = std::nullopt;
ValidatedLedgerSection validatedLedger = {};

View File

@@ -2,7 +2,8 @@ add_library(clio_util)
target_sources(
clio_util
PRIVATE config/Config.cpp
PRIVATE build/Build.cpp
config/Config.cpp
log/Logger.cpp
prometheus/Http.cpp
prometheus/Label.cpp

42
src/util/OverloadSet.hpp Normal file
View File

@@ -0,0 +1,42 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
namespace util {
/**
* @brief Overload set for lambdas
*
* @tparam Ts Types of lambdas
*/
template <typename... Ts>
struct OverloadSet : Ts... {
using Ts::operator()...;
};
/**
* @brief Deduction guide for OverloadSet
*
* @tparam Ts Types of lambdas
*/
template <class... Ts>
OverloadSet(Ts...) -> OverloadSet<Ts...>;
} // namespace util

View File

@@ -21,7 +21,7 @@
#include <string>
namespace Build {
namespace util::build {
std::string const&
getClioVersionString();
@@ -29,4 +29,4 @@ getClioVersionString();
std::string const&
getClioFullVersionString();
} // namespace Build
} // namespace util::build

View File

@@ -82,9 +82,9 @@ getRootCertificate()
} // namespace
std::expected<boost::asio::ssl::context, RequestError>
makeSslContext()
makeClientSslContext()
{
ssl::context context{ssl::context::sslv23_client};
ssl::context context{ssl::context::tls_client};
context.set_verify_mode(ssl::verify_peer);
auto const rootCertificate = getRootCertificate();
if (not rootCertificate.has_value()) {

View File

@@ -31,7 +31,7 @@
namespace util::requests::impl {
std::expected<boost::asio::ssl::context, RequestError>
makeSslContext();
makeClientSslContext();
std::optional<std::string>
sslErrorToString(boost::beast::error_code const& error);

View File

@@ -61,7 +61,7 @@ public:
static std::expected<SslStreamData, RequestError>
create(boost::asio::yield_context yield)
{
auto sslContext = makeSslContext();
auto sslContext = makeClientSslContext();
if (not sslContext.has_value()) {
return std::unexpected{std::move(sslContext.error())};
}

View File

@@ -1,5 +1,8 @@
add_library(clio_web)
target_sources(clio_web PRIVATE impl/AdminVerificationStrategy.cpp IntervalSweepHandler.cpp Resolver.cpp)
target_sources(
clio_web PRIVATE impl/AdminVerificationStrategy.cpp impl/ServerSslContext.cpp IntervalSweepHandler.cpp Resolver.cpp
Server.cpp
)
target_link_libraries(clio_web PUBLIC clio_util)

48
src/web/Server.cpp Normal file
View File

@@ -0,0 +1,48 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "web/Server.hpp"
#include "util/config/Config.hpp"
#include <boost/asio/ssl/context.hpp>
#include <optional>
#include <string>
namespace web {
std::expected<std::optional<boost::asio::ssl::context>, std::string>
makeServerSslContext(util::Config const& config)
{
bool const configHasCertFile = config.contains("ssl_cert_file");
bool const configHasKeyFile = config.contains("ssl_key_file");
if (configHasCertFile != configHasKeyFile)
return std::unexpected{"Config entries 'ssl_cert_file' and 'ssl_key_file' must be set or unset together."};
if (not configHasCertFile)
return std::nullopt;
auto const certFilename = config.value<std::string>("ssl_cert_file");
auto const keyFilename = config.value<std::string>("ssl_key_file");
return impl::makeServerSslContext(certFilename, keyFilename);
}
} // namespace web

View File

@@ -24,6 +24,7 @@
#include "web/DOSGuard.hpp"
#include "web/HttpSession.hpp"
#include "web/SslHttpSession.hpp"
#include "web/impl/ServerSslContext.hpp"
#include "web/interface/Concepts.hpp"
#include <boost/asio/io_context.hpp>
@@ -58,6 +59,15 @@
*/
namespace web {
/**
* @brief A helper function to create a server SSL context.
*
* @param config The config to create the context
* @return Optional SSL context or error message if any
*/
std::expected<std::optional<boost::asio::ssl::context>, std::string>
makeServerSslContext(util::Config const& config);
/**
* @brief The Detector class to detect if the connection is a ssl or not.
*
@@ -201,7 +211,7 @@ class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslS
util::Logger log_{"WebServer"};
std::reference_wrapper<boost::asio::io_context> ioc_;
std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx_;
std::optional<boost::asio::ssl::context> ctx_;
util::TagDecoratorFactory tagFactory_;
std::reference_wrapper<web::DOSGuard> dosGuard_;
std::shared_ptr<HandlerType> handler_;
@@ -222,7 +232,7 @@ public:
*/
Server(
boost::asio::io_context& ioc,
std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx,
std::optional<boost::asio::ssl::context> ctx,
tcp::endpoint endpoint,
util::TagDecoratorFactory tagFactory,
web::DOSGuard& dosGuard,
@@ -230,7 +240,7 @@ public:
std::optional<std::string> adminPassword
)
: ioc_(std::ref(ioc))
, ctx_(ctx)
, ctx_(std::move(ctx))
, tagFactory_(tagFactory)
, dosGuard_(std::ref(dosGuard))
, handler_(std::move(handler))
@@ -285,7 +295,8 @@ private:
onAccept(boost::beast::error_code ec, tcp::socket socket)
{
if (!ec) {
auto ctxRef = ctx_ ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_} : std::nullopt;
auto ctxRef =
ctx_ ? std::optional<std::reference_wrapper<boost::asio::ssl::context>>{ctx_.value()} : std::nullopt;
std::make_shared<Detector<PlainSessionType, SslSessionType, HandlerType>>(
std::move(socket), ctxRef, std::cref(tagFactory_), dosGuard_, handler_, adminVerification_
@@ -307,7 +318,6 @@ using HttpServer = Server<HttpSession, SslHttpSession, HandlerType>;
* @tparam HandlerType The tyep of handler to process the request
* @param config The config to create server
* @param ioc The server will run under this io_context
* @param ctx The SSL context if any
* @param dosGuard The dos guard to protect the server
* @param handler The handler to process the request
* @return The server instance
@@ -317,12 +327,18 @@ static std::shared_ptr<HttpServer<HandlerType>>
make_HttpServer(
util::Config const& config,
boost::asio::io_context& ioc,
std::optional<std::reference_wrapper<boost::asio::ssl::context>> const& ctx,
web::DOSGuard& dosGuard,
std::shared_ptr<HandlerType> const& handler
)
{
static util::Logger const log{"WebServer"};
auto expectedSslContext = makeServerSslContext(config);
if (not expectedSslContext) {
LOG(log.error()) << "Failed to create SSL context: " << expectedSslContext.error();
return nullptr;
}
if (!config.contains("server"))
return nullptr;
@@ -347,7 +363,7 @@ make_HttpServer(
auto server = std::make_shared<HttpServer<HandlerType>>(
ioc,
ctx,
std::move(expectedSslContext).value(),
boost::asio::ip::tcp::endpoint{address, port},
util::TagDecoratorFactory(config),
dosGuard,

View File

@@ -19,9 +19,9 @@
#pragma once
#include "main/Build.hpp"
#include "rpc/Errors.hpp"
#include "util/Taggable.hpp"
#include "util/build/Build.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Http.hpp"
#include "web/DOSGuard.hpp"
@@ -297,7 +297,7 @@ private:
httpResponse(http::status status, std::string content_type, std::string message) const
{
http::response<http::string_body> res{status, req_.version()};
res.set(http::field::server, "clio-server-" + Build::getClioVersionString());
res.set(http::field::server, "clio-server-" + util::build::getClioVersionString());
res.set(http::field::content_type, content_type);
res.keep_alive(req_.keep_alive());
res.body() = std::move(message);

View File

@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "web/impl/ServerSslContext.hpp"
#include <boost/asio/buffer.hpp>
#include <boost/asio/ssl/context.hpp>
#include <fmt/compile.h>
#include <fmt/core.h>
#include <expected>
#include <fstream>
#include <ios>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
namespace web::impl {
namespace {
std::optional<std::string>
readFile(std::string const& path)
{
std::ifstream const file(path, std::ios::in | std::ios::binary);
if (!file)
return {};
std::stringstream contents;
contents << file.rdbuf();
return std::move(contents).str();
}
} // namespace
std::expected<boost::asio::ssl::context, std::string>
makeServerSslContext(std::string const& certFilePath, std::string const& keyFilePath)
{
auto const certContent = readFile(certFilePath);
if (!certContent)
return std::unexpected{"Can't read SSL certificate: " + certFilePath};
auto const keyContent = readFile(keyFilePath);
if (!keyContent)
return std::unexpected{"Can't read SSL key: " + keyFilePath};
using namespace boost::asio;
ssl::context ctx{ssl::context::tls_server};
ctx.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2);
try {
ctx.use_certificate_chain(buffer(certContent->data(), certContent->size()));
ctx.use_private_key(buffer(keyContent->data(), keyContent->size()), ssl::context::file_format::pem);
} catch (...) {
return std::unexpected{
fmt::format("Error loading SSL certificate ({}) or SSL key ({}).", certFilePath, keyFilePath)
};
}
return ctx;
}
} // namespace web::impl

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#pragma once
#include <boost/asio/ssl/context.hpp>
#include <expected>
#include <string>
namespace web::impl {
std::expected<boost::asio::ssl::context, std::string>
makeServerSslContext(std::string const& certFilePath, std::string const& keyFilePath);
} // namespace web::impl

View File

@@ -8,4 +8,4 @@ target_sources(
include(deps/gtest)
target_include_directories(clio_testing_common PUBLIC .)
target_link_libraries(clio_testing_common PUBLIC clio gtest::gtest)
target_link_libraries(clio_testing_common PUBLIC clio_app gtest::gtest)

View File

@@ -4,6 +4,7 @@ target_sources(
clio_tests
PRIVATE # Common
ConfigTests.cpp
app/CliArgsTests.cpp
data/AmendmentCenterTests.cpp
data/BackendCountersTests.cpp
data/BackendInterfaceTests.cpp
@@ -125,12 +126,19 @@ target_sources(
util/TxUtilTests.cpp
# Webserver
web/AdminVerificationTests.cpp
web/impl/ServerSslContextTests.cpp
web/RPCServerHandlerTests.cpp
web/ServerTests.cpp
web/SweepHandlerTests.cpp
web/WhitelistHandlerTests.cpp
)
configure_file(test_data/cert.pem ${CMAKE_BINARY_DIR}/tests/unit/test_data/cert.pem COPYONLY)
target_compile_definitions(clio_tests PRIVATE TEST_DATA_SSL_CERT_PATH="tests/unit/test_data/cert.pem")
configure_file(test_data/key.pem ${CMAKE_BINARY_DIR}/tests/unit/test_data/key.pem COPYONLY)
target_compile_definitions(clio_tests PRIVATE TEST_DATA_SSL_KEY_PATH="tests/unit/test_data/key.pem")
# See https://github.com/google/googletest/issues/3475
gtest_discover_tests(clio_tests DISCOVERY_TIMEOUT 90)

View File

@@ -0,0 +1,76 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "app/CliArgs.hpp"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <array>
#include <cstdlib>
#include <string_view>
using namespace app;
struct CliArgsTests : testing::Test {
testing::StrictMock<testing::MockFunction<int(CliArgs::Action::Run)>> onRunMock;
testing::StrictMock<testing::MockFunction<int(CliArgs::Action::Exit)>> onExitMock;
};
TEST_F(CliArgsTests, Parse_NoArgs)
{
std::array argv{"clio_server"};
auto const action = CliArgs::parse(argv.size(), argv.data());
int const returnCode = 123;
EXPECT_CALL(onRunMock, Call).WillOnce([](CliArgs::Action::Run const& run) {
EXPECT_EQ(run.configPath, CliArgs::defaultConfigPath);
return returnCode;
});
EXPECT_EQ(action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction()), returnCode);
}
TEST_F(CliArgsTests, Parse_VersionHelp)
{
for (auto& argv :
{std::array{"clio_server", "--version"},
std::array{"clio_server", "-v"},
std::array{"clio_server", "--help"},
std::array{"clio_server", "-h"}}) {
auto const action = CliArgs::parse(argv.size(), const_cast<char const**>(argv.data()));
EXPECT_CALL(onExitMock, Call).WillOnce([](CliArgs::Action::Exit const& exit) { return exit.exitCode; });
EXPECT_EQ(action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction()), EXIT_SUCCESS);
}
}
TEST_F(CliArgsTests, Parse_Config)
{
std::string_view configPath = "some_config_path";
std::array argv{"clio_server", "--conf", configPath.data()};
auto const action = CliArgs::parse(argv.size(), argv.data());
int const returnCode = 123;
EXPECT_CALL(onRunMock, Call).WillOnce([&configPath](CliArgs::Action::Run const& run) {
EXPECT_EQ(run.configPath, configPath);
return returnCode;
});
EXPECT_EQ(action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction()), returnCode);
}

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIJAOE4Hv/P8CO3MA0GCSqGSIb3DQEBCwUAMDkxEjAQBgNV
BAMMCTEyNy4wLjAuMTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFuc2lz
Y28wHhcNMjMwNTE4MTUwMzEwWhcNMjQwNTE3MTUwMzEwWjBrMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5zaXNjbzEN
MAsGA1UECgwEVGVzdDEMMAoGA1UECwwDRGV2MRIwEAYDVQQDDAkxMjcuMC4wLjEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo/crhYMiGTrfNvFKg3y0m
pFkPdbQhYUzAKW5lyFTCwc/EQLjfaw+TnxiifKdjmca1N5IaF51KocPSAUEtxT+y
7h1KyP6SAaAnAqaI+ahCJOnMSZ2DYqquevDpACKXKHIyCOjqVg6IKwtTap2ddw3w
A5oAP3C2o11ygUVAkP29T24oDzF6/AgXs6ClTIRGWePkgtMaXDM6vUihyGnEbTwk
PbYL1mVIsHYNMZtbjHw692hsC0K0pT7H2FFuBoA3+OAfN74Ks3cGrjxFjZLnU979
WsOdMBagMn9VUW+/zPieIALl1gKgB0Hpm63XVtROymqnwxa3eDMSndnVwqzzd+1p
AgMBAAGjgYYwgYMwUwYDVR0jBEwwSqE9pDswOTESMBAGA1UEAwwJMTI3LjAuMC4x
MQswCQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5zaXNjb4IJAKu2wr50Pfbq
MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQGA1UdEQQNMAuCCTEyNy4wLjAuMTAN
BgkqhkiG9w0BAQsFAAOCAQEArEjC1DmJ6q0735PxGkOmjWNsfnw8c2Zl1Z4idKfn
svEFtegNLU7tCu4aKunxlCHWiFVpunr4X67qH1JiE93W0JADnRrPxvywiqR6nUcO
p6HII/kzOizUXk59QMc1GLIIR6LDlNEeDlUbIc2DH8DPrRFBuIMYy4lf18qyfiUb
8Jt8nLeAzbhA21wI6BVhEt8G/cgIi88mPifXq+YVHrJE01jUREHRwl/MMildqxgp
LLuOOuPuy2d+HqjKE7z00j28Uf7gZK29bGx1rK+xH6veAr4plKBavBr8WWpAoUG+
PAMNb1i80cMsjK98xXDdr+7Uvy5M4COMwA5XHmMZDEW8Jw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqP3K4WDIhk63zbxSoN8tJqRZD3W0IWFMwCluZchUwsHPxEC4
32sPk58YonynY5nGtTeSGhedSqHD0gFBLcU/su4dSsj+kgGgJwKmiPmoQiTpzEmd
g2Kqrnrw6QAilyhyMgjo6lYOiCsLU2qdnXcN8AOaAD9wtqNdcoFFQJD9vU9uKA8x
evwIF7OgpUyERlnj5ILTGlwzOr1IochpxG08JD22C9ZlSLB2DTGbW4x8OvdobAtC
tKU+x9hRbgaAN/jgHze+CrN3Bq48RY2S51Pe/VrDnTAWoDJ/VVFvv8z4niAC5dYC
oAdB6Zut11bUTspqp8MWt3gzEp3Z1cKs83ftaQIDAQABAoIBAGXZH48Zz4DyrGA4
YexG1WV2o55np/p+M82Uqs55IGyIdnmnMESmt6qWtjgnvJKQuWu6ZDmJhejW+bf1
vZyiRrPGQq0x2guRIz6foFLpdHj42lee/mmS659gxRUIWdCUNc7mA8pHt1Zl6tuJ
ZBjlCedfpE8F7R6F8unx8xTozaRr4ZbOVnqB8YWjyuIDUnujsxKdKFASZJAEzRjh
+lScXAdEYTaswgTWFFGKzwTjH/Yfv4y3LwE0RmR/1e+eQmQ7Z4C0HhjYe3EYXAvk
naH2QFZaYVhu7x/+oLPetIzFJOZn61iDhUtGYdvQVvF8qQCPqeuKeLcS9X5my9aK
nfLUryECgYEA3ZZGffe6Me6m0ZX/zwT5NbZpZCJgeALGLZPg9qulDVf8zHbDRsdn
K6Mf/Xhy3DCfSwdwcuAKz/r+4tPFyNUJR+Y2ltXaVl72iY3uJRdriNrEbZ47Ez4z
dhtEmDrD7C+7AusErEgjas+AKXkp1tovXrXUiVfRytBtoKqrym4IjJUCgYEAwzxz
fTuE2nrIwFkvg0p9PtrCwkw8dnzhBeNnzFdPOVAiHCfnNcaSOWWTkGHIkGLoORqs
fqfZCD9VkqRwsPDaSSL7vhX3oHuerDipdxOjaXVjYa7YjM6gByzo62hnG6BcQHC7
zrj7iqjnMdyNLtXcPu6zm/j5iIOLWXMevK/OVIUCgYAey4e4cfk6f0RH1GTczIAl
6tfyxqRJiXkpVGfrYCdsF1JWyBqTd5rrAZysiVTNLSS2NK54CJL4HJXXyD6wjorf
pyrnA4l4f3Ib49G47exP9Ldf1KG5JufX/iomTeR0qp1+5lKb7tqdOYFCQkiCR4hV
zUdgXwgU+6qArbd6RpiBkQKBgQCSen5jjQ5GJS0NM1y0cmS5jcPlpvEOLO9fTZiI
9VCZPYf5++46qHr42T73aoXh3nNAtMSKWkA5MdtwJDPwbSQ5Dyg1G6IoI9eOewya
LH/EFbC0j0wliLkD6SvvwurpDU1pg6tElAEVrVeYT1MVupp+FPVopkoBpEAeooKD
KpvxSQKBgQDP9fNJIpuX3kaudb0pI1OvuqBYTrTExMx+JMR+Sqf0HUwavpeCn4du
O2R4tGOOkGAX/0/actRXptFk23ucHnSIwcW6HYgDM3tDBP7n3GYdu5CSE1eiR5k7
Zl3fuvbMYcmYKgutFcRj+8NvzRWT2suzGU2x4PiPX+fh5kpvmMdvLA==
-----END RSA PRIVATE KEY-----

View File

@@ -25,6 +25,6 @@ using namespace util::requests::impl;
TEST(SslContext, Create)
{
auto ctx = makeSslContext();
auto ctx = makeClientSslContext();
EXPECT_TRUE(ctx);
}

View File

@@ -40,13 +40,13 @@
#include <boost/beast/http/status.hpp>
#include <boost/beast/websocket/error.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/value.hpp>
#include <boost/system/system_error.hpp>
#include <fmt/core.h>
#include <gtest/gtest.h>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
@@ -60,12 +60,11 @@
using namespace util;
using namespace web::impl;
using namespace web;
using namespace boost::json;
std::string
boost::json::value
generateJSONWithDynamicPort(std::string_view port)
{
return fmt::format(
return boost::json::parse(fmt::format(
R"JSON({{
"server": {{
"ip": "0.0.0.0",
@@ -80,13 +79,13 @@ generateJSONWithDynamicPort(std::string_view port)
}}
}})JSON",
port
);
));
}
std::string
boost::json::value
generateJSONDataOverload(std::string_view port)
{
return fmt::format(
return boost::json::parse(fmt::format(
R"JSON({{
"server": {{
"ip": "0.0.0.0",
@@ -100,71 +99,18 @@ generateJSONDataOverload(std::string_view port)
}}
}})JSON",
port
);
));
}
// for testing, we use a self-signed certificate
std::optional<ssl::context>
parseCertsForTest()
boost::json::value
addSslConfig(boost::json::value config)
{
std::string const key = R"(-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqP3K4WDIhk63zbxSoN8tJqRZD3W0IWFMwCluZchUwsHPxEC4
32sPk58YonynY5nGtTeSGhedSqHD0gFBLcU/su4dSsj+kgGgJwKmiPmoQiTpzEmd
g2Kqrnrw6QAilyhyMgjo6lYOiCsLU2qdnXcN8AOaAD9wtqNdcoFFQJD9vU9uKA8x
evwIF7OgpUyERlnj5ILTGlwzOr1IochpxG08JD22C9ZlSLB2DTGbW4x8OvdobAtC
tKU+x9hRbgaAN/jgHze+CrN3Bq48RY2S51Pe/VrDnTAWoDJ/VVFvv8z4niAC5dYC
oAdB6Zut11bUTspqp8MWt3gzEp3Z1cKs83ftaQIDAQABAoIBAGXZH48Zz4DyrGA4
YexG1WV2o55np/p+M82Uqs55IGyIdnmnMESmt6qWtjgnvJKQuWu6ZDmJhejW+bf1
vZyiRrPGQq0x2guRIz6foFLpdHj42lee/mmS659gxRUIWdCUNc7mA8pHt1Zl6tuJ
ZBjlCedfpE8F7R6F8unx8xTozaRr4ZbOVnqB8YWjyuIDUnujsxKdKFASZJAEzRjh
+lScXAdEYTaswgTWFFGKzwTjH/Yfv4y3LwE0RmR/1e+eQmQ7Z4C0HhjYe3EYXAvk
naH2QFZaYVhu7x/+oLPetIzFJOZn61iDhUtGYdvQVvF8qQCPqeuKeLcS9X5my9aK
nfLUryECgYEA3ZZGffe6Me6m0ZX/zwT5NbZpZCJgeALGLZPg9qulDVf8zHbDRsdn
K6Mf/Xhy3DCfSwdwcuAKz/r+4tPFyNUJR+Y2ltXaVl72iY3uJRdriNrEbZ47Ez4z
dhtEmDrD7C+7AusErEgjas+AKXkp1tovXrXUiVfRytBtoKqrym4IjJUCgYEAwzxz
fTuE2nrIwFkvg0p9PtrCwkw8dnzhBeNnzFdPOVAiHCfnNcaSOWWTkGHIkGLoORqs
fqfZCD9VkqRwsPDaSSL7vhX3oHuerDipdxOjaXVjYa7YjM6gByzo62hnG6BcQHC7
zrj7iqjnMdyNLtXcPu6zm/j5iIOLWXMevK/OVIUCgYAey4e4cfk6f0RH1GTczIAl
6tfyxqRJiXkpVGfrYCdsF1JWyBqTd5rrAZysiVTNLSS2NK54CJL4HJXXyD6wjorf
pyrnA4l4f3Ib49G47exP9Ldf1KG5JufX/iomTeR0qp1+5lKb7tqdOYFCQkiCR4hV
zUdgXwgU+6qArbd6RpiBkQKBgQCSen5jjQ5GJS0NM1y0cmS5jcPlpvEOLO9fTZiI
9VCZPYf5++46qHr42T73aoXh3nNAtMSKWkA5MdtwJDPwbSQ5Dyg1G6IoI9eOewya
LH/EFbC0j0wliLkD6SvvwurpDU1pg6tElAEVrVeYT1MVupp+FPVopkoBpEAeooKD
KpvxSQKBgQDP9fNJIpuX3kaudb0pI1OvuqBYTrTExMx+JMR+Sqf0HUwavpeCn4du
O2R4tGOOkGAX/0/actRXptFk23ucHnSIwcW6HYgDM3tDBP7n3GYdu5CSE1eiR5k7
Zl3fuvbMYcmYKgutFcRj+8NvzRWT2suzGU2x4PiPX+fh5kpvmMdvLA==
-----END RSA PRIVATE KEY-----)";
std::string const cert = R"(-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIJAOE4Hv/P8CO3MA0GCSqGSIb3DQEBCwUAMDkxEjAQBgNV
BAMMCTEyNy4wLjAuMTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNhbiBGcmFuc2lz
Y28wHhcNMjMwNTE4MTUwMzEwWhcNMjQwNTE3MTUwMzEwWjBrMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5zaXNjbzEN
MAsGA1UECgwEVGVzdDEMMAoGA1UECwwDRGV2MRIwEAYDVQQDDAkxMjcuMC4wLjEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo/crhYMiGTrfNvFKg3y0m
pFkPdbQhYUzAKW5lyFTCwc/EQLjfaw+TnxiifKdjmca1N5IaF51KocPSAUEtxT+y
7h1KyP6SAaAnAqaI+ahCJOnMSZ2DYqquevDpACKXKHIyCOjqVg6IKwtTap2ddw3w
A5oAP3C2o11ygUVAkP29T24oDzF6/AgXs6ClTIRGWePkgtMaXDM6vUihyGnEbTwk
PbYL1mVIsHYNMZtbjHw692hsC0K0pT7H2FFuBoA3+OAfN74Ks3cGrjxFjZLnU979
WsOdMBagMn9VUW+/zPieIALl1gKgB0Hpm63XVtROymqnwxa3eDMSndnVwqzzd+1p
AgMBAAGjgYYwgYMwUwYDVR0jBEwwSqE9pDswOTESMBAGA1UEAwwJMTI3LjAuMC4x
MQswCQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5zaXNjb4IJAKu2wr50Pfbq
MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMBQGA1UdEQQNMAuCCTEyNy4wLjAuMTAN
BgkqhkiG9w0BAQsFAAOCAQEArEjC1DmJ6q0735PxGkOmjWNsfnw8c2Zl1Z4idKfn
svEFtegNLU7tCu4aKunxlCHWiFVpunr4X67qH1JiE93W0JADnRrPxvywiqR6nUcO
p6HII/kzOizUXk59QMc1GLIIR6LDlNEeDlUbIc2DH8DPrRFBuIMYy4lf18qyfiUb
8Jt8nLeAzbhA21wI6BVhEt8G/cgIi88mPifXq+YVHrJE01jUREHRwl/MMildqxgp
LLuOOuPuy2d+HqjKE7z00j28Uf7gZK29bGx1rK+xH6veAr4plKBavBr8WWpAoUG+
PAMNb1i80cMsjK98xXDdr+7Uvy5M4COMwA5XHmMZDEW8Jw==
-----END CERTIFICATE-----)";
ssl::context ctx{ssl::context::tlsv12};
ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2);
ctx.use_certificate_chain(boost::asio::buffer(cert.data(), cert.size()));
ctx.use_private_key(boost::asio::buffer(key.data(), key.size()), boost::asio::ssl::context::file_format::pem);
return ctx;
config.as_object()["ssl_key_file"] = TEST_DATA_SSL_KEY_PATH;
config.as_object()["ssl_cert_file"] = TEST_DATA_SSL_CERT_PATH;
return config;
}
class WebServerTest : public NoLoggerFixture {
public:
struct WebServerTest : NoLoggerFixture {
~WebServerTest() override
{
work.reset();
@@ -173,7 +119,6 @@ public:
runner->join();
}
protected:
WebServerTest()
{
work.emplace(ctx); // make sure ctx does not stop on its own
@@ -183,12 +128,12 @@ protected:
// this ctx is for dos timer
boost::asio::io_context ctxSync;
std::string const port = std::to_string(tests::util::generateFreePort());
Config cfg{parse(generateJSONWithDynamicPort(port))};
Config cfg{generateJSONWithDynamicPort(port)};
IntervalSweepHandler sweepHandler = web::IntervalSweepHandler{cfg, ctxSync};
WhitelistHandler whitelistHandler = web::WhitelistHandler{cfg};
DOSGuard dosGuard = web::DOSGuard{cfg, whitelistHandler, sweepHandler};
Config cfgOverload{parse(generateJSONDataOverload(port))};
Config cfgOverload{generateJSONDataOverload(port)};
IntervalSweepHandler sweepHandlerOverload = web::IntervalSweepHandler{cfgOverload, ctxSync};
WhitelistHandler whitelistHandlerOverload = web::WhitelistHandler{cfgOverload};
DOSGuard dosGuardOverload = web::DOSGuard{cfgOverload, whitelistHandlerOverload, sweepHandlerOverload};
@@ -235,7 +180,6 @@ std::shared_ptr<web::HttpServer<Executor>>
makeServerSync(
util::Config const& config,
boost::asio::io_context& ioc,
std::optional<std::reference_wrapper<boost::asio::ssl::context>> const& sslCtx,
web::DOSGuard& dosGuard,
std::shared_ptr<Executor> const& handler
)
@@ -245,7 +189,7 @@ makeServerSync(
std::condition_variable cv;
bool ready = false;
boost::asio::dispatch(ioc.get_executor(), [&]() mutable {
server = web::make_HttpServer(config, ioc, sslCtx, dosGuard, handler);
server = web::make_HttpServer(config, ioc, dosGuard, handler);
{
std::lock_guard const lk(m);
ready = true;
@@ -264,7 +208,7 @@ makeServerSync(
TEST_F(WebServerTest, Http)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e);
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
auto const res = HttpSyncClient::syncPost("localhost", port, R"({"Hello":1})");
EXPECT_EQ(res, R"({"Hello":1})");
}
@@ -272,7 +216,7 @@ TEST_F(WebServerTest, Http)
TEST_F(WebServerTest, Ws)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e);
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
WebSocketSyncClient wsClient;
wsClient.connect("localhost", port);
auto const res = wsClient.syncPost(R"({"Hello":1})");
@@ -283,7 +227,7 @@ TEST_F(WebServerTest, Ws)
TEST_F(WebServerTest, HttpInternalError)
{
auto e = std::make_shared<ExceptionExecutor>();
auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e);
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
auto const res = HttpSyncClient::syncPost("localhost", port, R"({})");
EXPECT_EQ(
res,
@@ -294,7 +238,7 @@ TEST_F(WebServerTest, HttpInternalError)
TEST_F(WebServerTest, WsInternalError)
{
auto e = std::make_shared<ExceptionExecutor>();
auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e);
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
WebSocketSyncClient wsClient;
wsClient.connect("localhost", port);
auto const res = wsClient.syncPost(R"({"id":"id1"})");
@@ -308,7 +252,7 @@ TEST_F(WebServerTest, WsInternalError)
TEST_F(WebServerTest, WsInternalErrorNotJson)
{
auto e = std::make_shared<ExceptionExecutor>();
auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuard, e);
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
WebSocketSyncClient wsClient;
wsClient.connect("localhost", port);
auto const res = wsClient.syncPost("not json");
@@ -319,12 +263,34 @@ TEST_F(WebServerTest, WsInternalErrorNotJson)
);
}
TEST_F(WebServerTest, IncompleteSslConfig)
{
auto e = std::make_shared<EchoExecutor>();
auto jsonConfig = generateJSONWithDynamicPort(port);
jsonConfig.as_object()["ssl_key_file"] = TEST_DATA_SSL_KEY_PATH;
auto const server = makeServerSync(Config{jsonConfig}, ctx, dosGuard, e);
EXPECT_EQ(server, nullptr);
}
TEST_F(WebServerTest, WrongSslConfig)
{
auto e = std::make_shared<EchoExecutor>();
auto jsonConfig = generateJSONWithDynamicPort(port);
jsonConfig.as_object()["ssl_key_file"] = TEST_DATA_SSL_KEY_PATH;
jsonConfig.as_object()["ssl_cert_file"] = "wrong_path";
auto const server = makeServerSync(Config{jsonConfig}, ctx, dosGuard, e);
EXPECT_EQ(server, nullptr);
}
TEST_F(WebServerTest, Https)
{
auto e = std::make_shared<EchoExecutor>();
auto sslCtx = parseCertsForTest();
auto const ctxSslRef = sslCtx ? std::optional<std::reference_wrapper<ssl::context>>{sslCtx.value()} : std::nullopt;
auto const server = makeServerSync(cfg, ctx, ctxSslRef, dosGuard, e);
cfg = Config{addSslConfig(generateJSONWithDynamicPort(port))};
auto const server = makeServerSync(cfg, ctx, dosGuard, e);
auto const res = HttpsSyncClient::syncPost("localhost", port, R"({"Hello":1})");
EXPECT_EQ(res, R"({"Hello":1})");
}
@@ -332,10 +298,8 @@ TEST_F(WebServerTest, Https)
TEST_F(WebServerTest, Wss)
{
auto e = std::make_shared<EchoExecutor>();
auto sslCtx = parseCertsForTest();
auto const ctxSslRef = sslCtx ? std::optional<std::reference_wrapper<ssl::context>>{sslCtx.value()} : std::nullopt;
auto server = makeServerSync(cfg, ctx, ctxSslRef, dosGuard, e);
cfg = Config{addSslConfig(generateJSONWithDynamicPort(port))};
auto server = makeServerSync(cfg, ctx, dosGuard, e);
WebServerSslSyncClient wsClient;
wsClient.connect("localhost", port);
auto const res = wsClient.syncPost(R"({"Hello":1})");
@@ -346,7 +310,7 @@ TEST_F(WebServerTest, Wss)
TEST_F(WebServerTest, HttpRequestOverload)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e);
auto const server = makeServerSync(cfg, ctx, dosGuardOverload, e);
auto res = HttpSyncClient::syncPost("localhost", port, R"({})");
EXPECT_EQ(res, "{}");
res = HttpSyncClient::syncPost("localhost", port, R"({})");
@@ -359,7 +323,7 @@ TEST_F(WebServerTest, HttpRequestOverload)
TEST_F(WebServerTest, WsRequestOverload)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e);
auto const server = makeServerSync(cfg, ctx, dosGuardOverload, e);
WebSocketSyncClient wsClient;
wsClient.connect("localhost", port);
auto res = wsClient.syncPost(R"({})");
@@ -379,7 +343,7 @@ TEST_F(WebServerTest, HttpPayloadOverload)
{
std::string const s100(100, 'a');
auto e = std::make_shared<EchoExecutor>();
auto server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e);
auto server = makeServerSync(cfg, ctx, dosGuardOverload, e);
auto const res = HttpSyncClient::syncPost("localhost", port, fmt::format(R"({{"payload":"{}"}})", s100));
EXPECT_EQ(
res,
@@ -391,7 +355,7 @@ TEST_F(WebServerTest, WsPayloadOverload)
{
std::string const s100(100, 'a');
auto e = std::make_shared<EchoExecutor>();
auto server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e);
auto server = makeServerSync(cfg, ctx, dosGuardOverload, e);
WebSocketSyncClient wsClient;
wsClient.connect("localhost", port);
auto const res = wsClient.syncPost(fmt::format(R"({{"payload":"{}"}})", s100));
@@ -405,7 +369,7 @@ TEST_F(WebServerTest, WsPayloadOverload)
TEST_F(WebServerTest, WsTooManyConnection)
{
auto e = std::make_shared<EchoExecutor>();
auto server = makeServerSync(cfg, ctx, std::nullopt, dosGuardOverload, e);
auto server = makeServerSync(cfg, ctx, dosGuardOverload, e);
// max connection is 2, exception should happen when the third connection is made
WebSocketSyncClient wsClient1;
wsClient1.connect("localhost", port);
@@ -485,7 +449,7 @@ JSONServerConfigWithNoSpecifiedAdmin(uint32_t const port)
}
// get this value from online sha256 generator
static auto constexpr SecertSha256 = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b";
static auto constexpr SecretSha256 = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b";
class AdminCheckExecutor {
public:
@@ -513,8 +477,8 @@ class WebServerAdminTest : public WebServerTest, public ::testing::WithParamInte
TEST_P(WebServerAdminTest, WsAdminCheck)
{
auto e = std::make_shared<AdminCheckExecutor>();
Config const serverConfig{parse(GetParam().config)};
auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuardOverload, e);
Config const serverConfig{boost::json::parse(GetParam().config)};
auto server = makeServerSync(serverConfig, ctx, dosGuardOverload, e);
WebSocketSyncClient wsClient;
uint32_t const webServerPort = serverConfig.value<uint32_t>("server.port");
wsClient.connect("localhost", std::to_string(webServerPort), GetParam().headers);
@@ -527,8 +491,8 @@ TEST_P(WebServerAdminTest, WsAdminCheck)
TEST_P(WebServerAdminTest, HttpAdminCheck)
{
auto e = std::make_shared<AdminCheckExecutor>();
Config const serverConfig{parse(GetParam().config)};
auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuardOverload, e);
Config const serverConfig{boost::json::parse(GetParam().config)};
auto server = makeServerSync(serverConfig, ctx, dosGuardOverload, e);
std::string const request = "Why hello";
uint32_t const webServerPort = serverConfig.value<uint32_t>("server.port");
auto const res = HttpSyncClient::syncPost("localhost", std::to_string(webServerPort), request, GetParam().headers);
@@ -556,27 +520,27 @@ INSTANTIATE_TEST_CASE_P(
},
WebServerAdminTestParams{
.config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()),
.headers = {WebHeader(http::field::authorization, SecertSha256)},
.headers = {WebHeader(http::field::authorization, SecretSha256)},
.expectedResponse = "user"
},
WebServerAdminTestParams{
.config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()),
.headers = {WebHeader(
http::field::authorization,
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256)
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecretSha256)
)},
.expectedResponse = "admin"
},
WebServerAdminTestParams{
.config = JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse(tests::util::generateFreePort()),
.headers = {WebHeader(http::field::authorization, SecertSha256)},
.headers = {WebHeader(http::field::authorization, SecretSha256)},
.expectedResponse = "user"
},
WebServerAdminTestParams{
.config = JSONServerConfigWithBothAdminPasswordAndLocalAdminFalse(tests::util::generateFreePort()),
.headers = {WebHeader(
http::field::authorization,
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256)
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecretSha256)
)},
.expectedResponse = "admin"
},
@@ -584,7 +548,7 @@ INSTANTIATE_TEST_CASE_P(
.config = JSONServerConfigWithAdminPassword(tests::util::generateFreePort()),
.headers = {WebHeader(
http::field::authentication_info,
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256)
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecretSha256)
)},
.expectedResponse = "user"
},
@@ -618,8 +582,8 @@ TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminSet)
);
auto e = std::make_shared<AdminCheckExecutor>();
Config const serverConfig{parse(JSONServerConfigWithBothAdminPasswordAndLocalAdmin)};
EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, std::nullopt, dosGuardOverload, e), std::logic_error);
Config const serverConfig{boost::json::parse(JSONServerConfigWithBothAdminPasswordAndLocalAdmin)};
EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, dosGuardOverload, e), std::logic_error);
}
TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminFalse)
@@ -637,8 +601,8 @@ TEST_F(WebServerTest, AdminErrorCfgTestBothAdminPasswordAndLocalAdminFalse)
);
auto e = std::make_shared<AdminCheckExecutor>();
Config const serverConfig{parse(JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse)};
EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, std::nullopt, dosGuardOverload, e), std::logic_error);
Config const serverConfig{boost::json::parse(JSONServerConfigWithNoAdminPasswordAndLocalAdminFalse)};
EXPECT_THROW(web::make_HttpServer(serverConfig, ctx, dosGuardOverload, e), std::logic_error);
}
struct WebServerPrometheusTest : util::prometheus::WithPrometheus, WebServerTest {};
@@ -647,8 +611,8 @@ TEST_F(WebServerPrometheusTest, rejectedWithoutAdminPassword)
{
auto e = std::make_shared<EchoExecutor>();
uint32_t const webServerPort = tests::util::generateFreePort();
Config const serverConfig{parse(JSONServerConfigWithAdminPassword(webServerPort))};
auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuard, e);
Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword(webServerPort))};
auto server = makeServerSync(serverConfig, ctx, dosGuard, e);
auto const res = HttpSyncClient::syncGet("localhost", std::to_string(webServerPort), "", "/metrics");
EXPECT_EQ(res, "Only admin is allowed to collect metrics");
}
@@ -669,9 +633,9 @@ TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled)
);
auto e = std::make_shared<EchoExecutor>();
Config const serverConfig{parse(JSONServerConfigWithDisabledPrometheus)};
Config const serverConfig{boost::json::parse(JSONServerConfigWithDisabledPrometheus)};
PrometheusService::init(serverConfig);
auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuard, e);
auto server = makeServerSync(serverConfig, ctx, dosGuard, e);
auto const res = HttpSyncClient::syncGet(
"localhost",
std::to_string(webServerPort),
@@ -679,7 +643,7 @@ TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled)
"/metrics",
{WebHeader(
http::field::authorization,
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256)
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecretSha256)
)}
);
EXPECT_EQ(res, "Prometheus is disabled in clio config");
@@ -691,8 +655,8 @@ TEST_F(WebServerPrometheusTest, validResponse)
auto& testCounter = PrometheusService::counterInt("test_counter", util::prometheus::Labels());
++testCounter;
auto e = std::make_shared<EchoExecutor>();
Config const serverConfig{parse(JSONServerConfigWithAdminPassword(webServerPort))};
auto server = makeServerSync(serverConfig, ctx, std::nullopt, dosGuard, e);
Config const serverConfig{boost::json::parse(JSONServerConfigWithAdminPassword(webServerPort))};
auto server = makeServerSync(serverConfig, ctx, dosGuard, e);
auto const res = HttpSyncClient::syncGet(
"localhost",
std::to_string(webServerPort),
@@ -700,7 +664,7 @@ TEST_F(WebServerPrometheusTest, validResponse)
"/metrics",
{WebHeader(
http::field::authorization,
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecertSha256)
fmt::format("{}{}", PasswordAdminVerificationStrategy::passwordPrefix, SecretSha256)
)}
);
EXPECT_EQ(res, "# TYPE test_counter counter\ntest_counter 1\n\n");

View File

@@ -0,0 +1,48 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, the clio developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "web/impl/ServerSslContext.hpp"
#include <gtest/gtest.h>
using namespace web::impl;
TEST(ServerSslContext, makeServerSslContext)
{
auto const sslContext = makeServerSslContext(TEST_DATA_SSL_CERT_PATH, TEST_DATA_SSL_KEY_PATH);
ASSERT_TRUE(sslContext);
}
TEST(ServerSslContext, makeServerSslContext_WrongCertPath)
{
auto const sslContext = makeServerSslContext("wrong_path", TEST_DATA_SSL_KEY_PATH);
ASSERT_FALSE(sslContext);
}
TEST(ServerSslContext, makeServerSslContext_WrongKeyPath)
{
auto const sslContext = makeServerSslContext(TEST_DATA_SSL_CERT_PATH, "wrong_path");
ASSERT_FALSE(sslContext);
}
TEST(ServerSslContext, makeServerSslContext_CertKeyMismatch)
{
auto const sslContext = makeServerSslContext(TEST_DATA_SSL_KEY_PATH, TEST_DATA_SSL_CERT_PATH);
ASSERT_FALSE(sslContext);
}