mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-21 20:25:52 +00:00
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
11
.github/workflows/nightly.yml
vendored
11
.github/workflows/nightly.yml
vendored
@@ -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
2
.gitignore
vendored
@@ -8,4 +8,4 @@
|
||||
.DS_Store
|
||||
CMakeUserPresets.json
|
||||
config.json
|
||||
src/main/impl/Build.cpp
|
||||
src/util/build/Build.cpp
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
4
src/app/CMakeLists.txt
Normal 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
70
src/app/CliArgs.cpp
Normal 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
96
src/app/CliArgs.hpp
Normal 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
142
src/app/ClioApplication.cpp
Normal 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
|
||||
51
src/app/ClioApplication.hpp
Normal file
51
src/app/ClioApplication.hpp
Normal 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
|
||||
@@ -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); }
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"}};
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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
42
src/util/OverloadSet.hpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())};
|
||||
}
|
||||
|
||||
@@ -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
48
src/web/Server.cpp
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
81
src/web/impl/ServerSslContext.cpp
Normal file
81
src/web/impl/ServerSslContext.cpp
Normal 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
|
||||
32
src/web/impl/ServerSslContext.hpp
Normal file
32
src/web/impl/ServerSslContext.hpp
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
76
tests/unit/app/CliArgsTests.cpp
Normal file
76
tests/unit/app/CliArgsTests.cpp
Normal 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);
|
||||
}
|
||||
22
tests/unit/test_data/cert.pem
Normal file
22
tests/unit/test_data/cert.pem
Normal 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-----
|
||||
27
tests/unit/test_data/key.pem
Normal file
27
tests/unit/test_data/key.pem
Normal 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-----
|
||||
@@ -25,6 +25,6 @@ using namespace util::requests::impl;
|
||||
|
||||
TEST(SslContext, Create)
|
||||
{
|
||||
auto ctx = makeSslContext();
|
||||
auto ctx = makeClientSslContext();
|
||||
EXPECT_TRUE(ctx);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
48
tests/unit/web/impl/ServerSslContextTests.cpp
Normal file
48
tests/unit/web/impl/ServerSslContextTests.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user