mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-19 03:05:51 +00:00
@@ -168,7 +168,7 @@ if(BUILD_TESTS)
|
||||
unittests/backend/cassandra/ExecutionStrategyTests.cpp
|
||||
unittests/backend/cassandra/AsyncExecutorTests.cpp
|
||||
unittests/webserver/ServerTest.cpp
|
||||
unittests/webserver/RPCExecutorTest.cpp)
|
||||
unittests/webserver/RPCServerHandlerTest.cpp)
|
||||
include(CMake/deps/gtest.cmake)
|
||||
|
||||
# fix for dwarf5 bug on ci
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include <rpc/Counters.h>
|
||||
#include <rpc/RPCEngine.h>
|
||||
#include <rpc/common/impl/HandlerProvider.h>
|
||||
#include <webserver/RPCExecutor.h>
|
||||
#include <webserver/RPCServerHandler.h>
|
||||
#include <webserver/Server.h>
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
@@ -206,11 +206,11 @@ try
|
||||
config, backend, subscriptions, balancer, etl, dosGuard, workQueue, counters, handlerProvider);
|
||||
|
||||
// init the web server
|
||||
auto executor =
|
||||
std::make_shared<RPCExecutor<RPC::RPCEngine, ETLService>>(config, backend, rpcEngine, etl, subscriptions);
|
||||
auto handler =
|
||||
std::make_shared<RPCServerHandler<RPC::RPCEngine, ETLService>>(config, backend, rpcEngine, etl, subscriptions);
|
||||
auto ctx = parseCerts(config);
|
||||
auto const ctxRef = ctx ? std::optional<std::reference_wrapper<ssl::context>>{ctx.value()} : std::nullopt;
|
||||
auto const httpServer = Server::make_HttpServer(config, ioc, ctxRef, dosGuard, executor);
|
||||
auto const httpServer = Server::make_HttpServer(config, ioc, ctxRef, dosGuard, handler);
|
||||
|
||||
// Blocks until stopped.
|
||||
// When stopped, shared_ptrs fall out of scope
|
||||
|
||||
@@ -81,6 +81,10 @@ getErrorInfo(ClioError code)
|
||||
{ClioError::rpcINVALID_HOT_WALLET, "invalidHotWallet", "Invalid hot wallet."},
|
||||
{ClioError::rpcUNKNOWN_OPTION, "unknownOption", "Unknown option."},
|
||||
{ClioError::rpcINVALID_API_VERSION, JS(invalid_API_version), "Invalid API version."},
|
||||
{ClioError::rpcCOMMAND_IS_MISSING, JS(missingCommand), "Method is not specified or is not a string."},
|
||||
{ClioError::rpcCOMMAND_NOT_STRING, "commandNotString", "Method is not a string."},
|
||||
{ClioError::rpcCOMMAND_IS_EMPTY, "emptyCommand", "Method is an empty string."},
|
||||
{ClioError::rpcPARAMS_UNPARSEABLE, "paramsUnparseable", "Params must be an array holding exactly one object."},
|
||||
};
|
||||
|
||||
auto matchByCode = [code](auto const& info) { return info.code == code; };
|
||||
|
||||
@@ -44,6 +44,10 @@ enum class ClioError {
|
||||
|
||||
// special system errors start with 6000
|
||||
rpcINVALID_API_VERSION = 6000,
|
||||
rpcCOMMAND_IS_MISSING = 6001,
|
||||
rpcCOMMAND_NOT_STRING = 6002,
|
||||
rpcCOMMAND_IS_EMPTY = 6003,
|
||||
rpcPARAMS_UNPARSEABLE = 6004,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -210,6 +214,15 @@ static Status OK;
|
||||
WarningInfo const&
|
||||
getWarningInfo(WarningCode code);
|
||||
|
||||
/**
|
||||
* @brief Get the error info object from an clio-specific error code.
|
||||
*
|
||||
* @param code The error code
|
||||
* @return ClioErrorInfo const& A reference to the static error info
|
||||
*/
|
||||
ClioErrorInfo const&
|
||||
getErrorInfo(ClioError code);
|
||||
|
||||
/**
|
||||
* @brief Generate JSON from a warning code.
|
||||
*
|
||||
|
||||
@@ -44,7 +44,7 @@ make_WsContext(
|
||||
commandValue = request.at("command");
|
||||
|
||||
if (!commandValue.is_string())
|
||||
return Error{{RippledError::rpcBAD_SYNTAX, "Method/Command is not specified or is not a string."}};
|
||||
return Error{{ClioError::rpcCOMMAND_IS_MISSING, "Method/Command is not specified or is not a string."}};
|
||||
|
||||
auto const apiVersion = apiVersionParser.get().parse(request);
|
||||
if (!apiVersion)
|
||||
@@ -65,21 +65,27 @@ make_HttpContext(
|
||||
{
|
||||
using Error = util::Unexpected<Status>;
|
||||
|
||||
if (!request.contains("method") || !request.at("method").is_string())
|
||||
return Error{{RippledError::rpcBAD_SYNTAX, "Method is not specified or is not a string."}};
|
||||
if (!request.contains("method"))
|
||||
return Error{{ClioError::rpcCOMMAND_IS_MISSING}};
|
||||
|
||||
string const& command = request.at("method").as_string().c_str();
|
||||
if (!request.at("method").is_string())
|
||||
return Error{{ClioError::rpcCOMMAND_NOT_STRING}};
|
||||
|
||||
if (request.at("method").as_string().empty())
|
||||
return Error{{ClioError::rpcCOMMAND_IS_EMPTY}};
|
||||
|
||||
string command = request.at("method").as_string().c_str();
|
||||
|
||||
if (command == "subscribe" || command == "unsubscribe")
|
||||
return Error{{RippledError::rpcBAD_SYNTAX, "Subscribe and unsubscribe are only allowed or websocket."}};
|
||||
|
||||
if (!request.at("params").is_array())
|
||||
return Error{{RippledError::rpcBAD_SYNTAX, "Missing params array."}};
|
||||
return Error{{ClioError::rpcPARAMS_UNPARSEABLE, "Missing params array."}};
|
||||
|
||||
boost::json::array const& array = request.at("params").as_array();
|
||||
|
||||
if (array.size() != 1 || !array.at(0).is_object())
|
||||
return Error{{RippledError::rpcBAD_SYNTAX, "Params must be an array holding exactly one object."}};
|
||||
return Error{{ClioError::rpcPARAMS_UNPARSEABLE}};
|
||||
|
||||
auto const apiVersion = apiVersionParser.get().parse(request.at("params").as_array().at(0).as_object());
|
||||
if (!apiVersion)
|
||||
|
||||
@@ -25,14 +25,17 @@
|
||||
#include <rpc/common/impl/APIVersionParser.h>
|
||||
#include <util/JsonUtils.h>
|
||||
#include <util/Profiler.h>
|
||||
#include <webserver/details/ErrorHandling.h>
|
||||
|
||||
#include <boost/json/parse.hpp>
|
||||
|
||||
/**
|
||||
* @brief The executor for RPC requests called by web server
|
||||
* @brief The server handler for RPC requests called by web server
|
||||
*
|
||||
* Note: see ServerHandler concept
|
||||
*/
|
||||
template <class Engine, class ETL>
|
||||
class RPCExecutor
|
||||
class RPCServerHandler
|
||||
{
|
||||
std::shared_ptr<BackendInterface const> const backend_;
|
||||
std::shared_ptr<Engine> const rpcEngine_;
|
||||
@@ -46,7 +49,7 @@ class RPCExecutor
|
||||
clio::Logger perfLog_{"Performance"};
|
||||
|
||||
public:
|
||||
RPCExecutor(
|
||||
RPCServerHandler(
|
||||
clio::Config const& config,
|
||||
std::shared_ptr<BackendInterface const> const& backend,
|
||||
std::shared_ptr<Engine> const& rpcEngine,
|
||||
@@ -84,18 +87,20 @@ public:
|
||||
connection->clientIp))
|
||||
{
|
||||
rpcEngine_->notifyTooBusy();
|
||||
connection->send(
|
||||
boost::json::serialize(RPC::makeError(RPC::RippledError::rpcTOO_BUSY)),
|
||||
boost::beast::http::status::ok);
|
||||
Server::detail::ErrorHelper(connection).sendTooBusyError();
|
||||
}
|
||||
}
|
||||
catch (boost::system::system_error const&)
|
||||
catch (boost::system::system_error const& ex)
|
||||
{
|
||||
// system_error thrown when json parsing failed
|
||||
rpcEngine_->notifyBadSyntax();
|
||||
connection->send(
|
||||
boost::json::serialize(RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX)),
|
||||
boost::beast::http::status::ok);
|
||||
Server::detail::ErrorHelper(connection).sendJsonParsingError(ex.what());
|
||||
}
|
||||
catch (std::invalid_argument const& ex)
|
||||
{
|
||||
// thrown when json parses something that is not an object at top level
|
||||
rpcEngine_->notifyBadSyntax();
|
||||
Server::detail::ErrorHelper(connection).sendJsonParsingError(ex.what());
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
@@ -129,20 +134,6 @@ private:
|
||||
<< " received request from work queue: " << util::removeSecret(request)
|
||||
<< " ip = " << connection->clientIp;
|
||||
|
||||
auto const id = request.contains("id") ? request.at("id") : nullptr;
|
||||
|
||||
auto const composeError = [&](auto const& error) -> boost::json::object {
|
||||
auto e = RPC::makeError(error);
|
||||
if (!id.is_null())
|
||||
e["id"] = id;
|
||||
e["request"] = request;
|
||||
|
||||
if (connection->upgraded)
|
||||
return e;
|
||||
else
|
||||
return {{"result", e}};
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
auto const range = backend_->fetchLedgerRange();
|
||||
@@ -150,9 +141,7 @@ private:
|
||||
{
|
||||
// for error that happened before the handler, we don't attach any warnings
|
||||
rpcEngine_->notifyNotReady();
|
||||
return connection->send(
|
||||
boost::json::serialize(composeError(RPC::RippledError::rpcNOT_READY)),
|
||||
boost::beast::http::status::ok);
|
||||
return Server::detail::ErrorHelper(connection, request).sendNotReadyError();
|
||||
}
|
||||
|
||||
auto const context = [&] {
|
||||
@@ -181,8 +170,10 @@ private:
|
||||
perfLog_.warn() << connection->tag() << "Could not create Web context: " << err;
|
||||
log_.warn() << connection->tag() << "Could not create Web context: " << err;
|
||||
|
||||
// we count all those as BadSyntax - as the WS path would.
|
||||
// Although over HTTP these will yield a 400 status with a plain text response (for most).
|
||||
rpcEngine_->notifyBadSyntax();
|
||||
return connection->send(boost::json::serialize(composeError(err)), boost::beast::http::status::ok);
|
||||
return Server::detail::ErrorHelper(connection, request).sendError(err);
|
||||
}
|
||||
|
||||
auto [v, timeDiff] = util::timed([&]() { return rpcEngine_->buildResponse(*context); });
|
||||
@@ -194,7 +185,7 @@ private:
|
||||
if (auto const status = std::get_if<RPC::Status>(&v))
|
||||
{
|
||||
// note: error statuses are counted/notified in buildResponse itself
|
||||
response = std::move(composeError(*status));
|
||||
response = Server::detail::ErrorHelper(connection, request).composeError(*status);
|
||||
auto const responseStr = boost::json::serialize(response);
|
||||
|
||||
perfLog_.debug() << context->tag() << "Encountered error: " << responseStr;
|
||||
@@ -225,10 +216,14 @@ private:
|
||||
// otherwise the "status" is in the "result" field
|
||||
if (connection->upgraded)
|
||||
{
|
||||
if (!id.is_null())
|
||||
auto const id = request.contains("id") ? request.at("id") : nullptr;
|
||||
|
||||
if (not id.is_null())
|
||||
response["id"] = id;
|
||||
|
||||
if (!response.contains("error"))
|
||||
response["status"] = "success";
|
||||
|
||||
response["type"] = "response";
|
||||
}
|
||||
else
|
||||
@@ -245,7 +240,7 @@ private:
|
||||
warnings.emplace_back(RPC::makeWarning(RPC::warnRPC_OUTDATED));
|
||||
|
||||
response["warnings"] = warnings;
|
||||
connection->send(boost::json::serialize(response), boost::beast::http::status::ok);
|
||||
connection->send(boost::json::serialize(response));
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
@@ -255,10 +250,7 @@ private:
|
||||
log_.error() << connection->tag() << "Caught exception: " << ex.what();
|
||||
|
||||
rpcEngine_->notifyInternalError();
|
||||
|
||||
return connection->send(
|
||||
boost::json::serialize(composeError(RPC::RippledError::rpcINTERNAL)),
|
||||
boost::beast::http::status::internal_server_error);
|
||||
return Server::detail::ErrorHelper(connection, request).sendInternalError();
|
||||
}
|
||||
}
|
||||
};
|
||||
160
src/webserver/details/ErrorHandling.h
Normal file
160
src/webserver/details/ErrorHandling.h
Normal file
@@ -0,0 +1,160 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2023, 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 <rpc/Errors.h>
|
||||
#include <webserver/interface/ConnectionBase.h>
|
||||
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Server::detail {
|
||||
|
||||
/**
|
||||
* @brief A helper that attempts to match rippled reporting mode HTTP errors as close as possible.
|
||||
*/
|
||||
class ErrorHelper
|
||||
{
|
||||
std::shared_ptr<Server::ConnectionBase> connection_;
|
||||
std::optional<boost::json::object> request_;
|
||||
|
||||
public:
|
||||
ErrorHelper(
|
||||
std::shared_ptr<Server::ConnectionBase> const& connection,
|
||||
std::optional<boost::json::object> request = std::nullopt)
|
||||
: connection_{connection}, request_{std::move(request)}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
sendError(RPC::Status const& err) const
|
||||
{
|
||||
if (connection_->upgraded)
|
||||
{
|
||||
connection_->send(boost::json::serialize(composeError(err)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: a collection of crutches to match rippled output follows
|
||||
if (auto const clioCode = std::get_if<RPC::ClioError>(&err.code))
|
||||
{
|
||||
switch (*clioCode)
|
||||
{
|
||||
case RPC::ClioError::rpcINVALID_API_VERSION:
|
||||
connection_->send(
|
||||
std::string{RPC::getErrorInfo(*clioCode).error}, boost::beast::http::status::bad_request);
|
||||
break;
|
||||
case RPC::ClioError::rpcCOMMAND_IS_MISSING:
|
||||
connection_->send("Null method", boost::beast::http::status::bad_request);
|
||||
break;
|
||||
case RPC::ClioError::rpcCOMMAND_IS_EMPTY:
|
||||
connection_->send("method is empty", boost::beast::http::status::bad_request);
|
||||
break;
|
||||
case RPC::ClioError::rpcCOMMAND_NOT_STRING:
|
||||
connection_->send("method is not string", boost::beast::http::status::bad_request);
|
||||
break;
|
||||
case RPC::ClioError::rpcPARAMS_UNPARSEABLE:
|
||||
connection_->send("params unparseable", boost::beast::http::status::bad_request);
|
||||
break;
|
||||
|
||||
// others are not applicable but we want a compilation error next time we add one
|
||||
case RPC::ClioError::rpcUNKNOWN_OPTION:
|
||||
case RPC::ClioError::rpcMALFORMED_CURRENCY:
|
||||
case RPC::ClioError::rpcMALFORMED_REQUEST:
|
||||
case RPC::ClioError::rpcMALFORMED_OWNER:
|
||||
case RPC::ClioError::rpcMALFORMED_ADDRESS:
|
||||
case RPC::ClioError::rpcINVALID_HOT_WALLET:
|
||||
assert(false); // this should never happen
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
connection_->send(boost::json::serialize(composeError(err)), boost::beast::http::status::bad_request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sendInternalError() const
|
||||
{
|
||||
connection_->send(
|
||||
boost::json::serialize(composeError(RPC::RippledError::rpcINTERNAL)),
|
||||
boost::beast::http::status::internal_server_error);
|
||||
}
|
||||
|
||||
void
|
||||
sendNotReadyError() const
|
||||
{
|
||||
connection_->send(
|
||||
boost::json::serialize(composeError(RPC::RippledError::rpcNOT_READY)), boost::beast::http::status::ok);
|
||||
}
|
||||
|
||||
void
|
||||
sendTooBusyError() const
|
||||
{
|
||||
if (connection_->upgraded)
|
||||
connection_->send(
|
||||
boost::json::serialize(RPC::makeError(RPC::RippledError::rpcTOO_BUSY)), boost::beast::http::status::ok);
|
||||
else
|
||||
connection_->send(
|
||||
boost::json::serialize(RPC::makeError(RPC::RippledError::rpcTOO_BUSY)),
|
||||
boost::beast::http::status::service_unavailable);
|
||||
}
|
||||
|
||||
void
|
||||
sendJsonParsingError(std::string_view reason) const
|
||||
{
|
||||
if (connection_->upgraded)
|
||||
connection_->send(
|
||||
boost::json::serialize(RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX)),
|
||||
boost::beast::http::status::ok);
|
||||
else
|
||||
connection_->send(
|
||||
fmt::format("Unable to parse request: {}", reason), boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
composeError(auto const& error) const
|
||||
{
|
||||
auto e = RPC::makeError(error);
|
||||
|
||||
if (request_)
|
||||
{
|
||||
auto const& req = request_.value();
|
||||
auto const id = req.contains("id") ? req.at("id") : nullptr;
|
||||
if (not id.is_null())
|
||||
e["id"] = id;
|
||||
|
||||
e["request"] = req;
|
||||
}
|
||||
|
||||
if (connection_->upgraded)
|
||||
return e;
|
||||
else
|
||||
return {{"result", e}};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Server::detail
|
||||
@@ -223,6 +223,7 @@ public:
|
||||
|
||||
auto sendError = [this](auto error, std::string&& requestStr) {
|
||||
auto e = RPC::makeError(error);
|
||||
|
||||
try
|
||||
{
|
||||
auto request = boost::json::parse(requestStr);
|
||||
@@ -235,29 +236,26 @@ public:
|
||||
e["request"] = std::move(requestStr);
|
||||
}
|
||||
|
||||
auto responseStr = boost::json::serialize(e);
|
||||
log_.trace() << responseStr;
|
||||
auto sharedMsg = std::make_shared<std::string>(std::move(responseStr));
|
||||
send(std::move(sharedMsg));
|
||||
this->send(std::make_shared<std::string>(boost::json::serialize(e)));
|
||||
};
|
||||
|
||||
std::string msg{static_cast<char const*>(buffer_.data().data()), buffer_.size()};
|
||||
std::string requestStr{static_cast<char const*>(buffer_.data().data()), buffer_.size()};
|
||||
|
||||
// dosGuard served request++ and check ip address
|
||||
if (!dosGuard_.get().request(clientIp))
|
||||
{
|
||||
// TODO: could be useful to count in counters in the future too
|
||||
sendError(RPC::RippledError::rpcSLOW_DOWN, std::move(msg));
|
||||
sendError(RPC::RippledError::rpcSLOW_DOWN, std::move(requestStr));
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
(*handler_)(msg, shared_from_this());
|
||||
(*handler_)(requestStr, shared_from_this());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
sendError(RPC::RippledError::rpcINTERNAL, std::move(msg));
|
||||
sendError(RPC::RippledError::rpcINTERNAL, std::move(requestStr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <util/Fixtures.h>
|
||||
#include <util/MockETLService.h>
|
||||
#include <util/MockRPCEngine.h>
|
||||
#include <webserver/RPCExecutor.h>
|
||||
#include <webserver/RPCServerHandler.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <gtest/gtest.h>
|
||||
@@ -32,17 +32,20 @@ constexpr static auto MAXSEQ = 30;
|
||||
struct MockWsBase : public Server::ConnectionBase
|
||||
{
|
||||
std::string message;
|
||||
boost::beast::http::status lastStatus = boost::beast::http::status::unknown;
|
||||
|
||||
void
|
||||
send(std::shared_ptr<std::string> msg_type) override
|
||||
{
|
||||
message += std::string(msg_type->data());
|
||||
lastStatus = boost::beast::http::status::ok;
|
||||
}
|
||||
|
||||
void
|
||||
send(std::string&& msg, boost::beast::http::status status = boost::beast::http::status::ok) override
|
||||
{
|
||||
message += std::string(msg.data());
|
||||
lastStatus = status;
|
||||
}
|
||||
|
||||
MockWsBase(util::TagDecoratorFactory const& factory) : Server::ConnectionBase(factory, "localhost.fake.ip")
|
||||
@@ -50,7 +53,7 @@ struct MockWsBase : public Server::ConnectionBase
|
||||
}
|
||||
};
|
||||
|
||||
class WebRPCExecutorTest : public MockBackendTest
|
||||
class WebRPCServerHandlerTest : public MockBackendTest
|
||||
{
|
||||
protected:
|
||||
void
|
||||
@@ -63,7 +66,7 @@ protected:
|
||||
tagFactory = std::make_shared<util::TagDecoratorFactory>(cfg);
|
||||
subManager = std::make_shared<SubscriptionManager>(cfg, mockBackendPtr);
|
||||
session = std::make_shared<MockWsBase>(*tagFactory);
|
||||
rpcExecutor = std::make_shared<RPCExecutor<MockAsyncRPCEngine, MockETLService>>(
|
||||
handler = std::make_shared<RPCServerHandler<MockAsyncRPCEngine, MockETLService>>(
|
||||
cfg, mockBackendPtr, rpcEngine, etl, subManager);
|
||||
}
|
||||
|
||||
@@ -77,12 +80,12 @@ protected:
|
||||
std::shared_ptr<MockETLService> etl;
|
||||
std::shared_ptr<SubscriptionManager> subManager;
|
||||
std::shared_ptr<util::TagDecoratorFactory> tagFactory;
|
||||
std::shared_ptr<RPCExecutor<MockAsyncRPCEngine, MockETLService>> rpcExecutor;
|
||||
std::shared_ptr<RPCServerHandler<MockAsyncRPCEngine, MockETLService>> handler;
|
||||
std::shared_ptr<MockWsBase> session;
|
||||
clio::Config cfg;
|
||||
};
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPDefaultPath)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPDefaultPath)
|
||||
{
|
||||
static auto constexpr request = R"({
|
||||
"method": "server_info",
|
||||
@@ -110,12 +113,12 @@ TEST_F(WebRPCExecutorTest, HTTPDefaultPath)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsNormalPath)
|
||||
TEST_F(WebRPCServerHandlerTest, WsNormalPath)
|
||||
{
|
||||
session->upgraded = true;
|
||||
static auto constexpr request = R"({
|
||||
@@ -145,12 +148,12 @@ TEST_F(WebRPCExecutorTest, WsNormalPath)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPForwardedPath)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPForwardedPath)
|
||||
{
|
||||
static auto constexpr request = R"({
|
||||
"method": "server_info",
|
||||
@@ -185,12 +188,12 @@ TEST_F(WebRPCExecutorTest, HTTPForwardedPath)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsForwardedPath)
|
||||
TEST_F(WebRPCServerHandlerTest, WsForwardedPath)
|
||||
{
|
||||
session->upgraded = true;
|
||||
static auto constexpr request = R"({
|
||||
@@ -228,12 +231,12 @@ TEST_F(WebRPCExecutorTest, WsForwardedPath)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPErrorPath)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPErrorPath)
|
||||
{
|
||||
static auto constexpr response = R"({
|
||||
"result": {
|
||||
@@ -275,12 +278,12 @@ TEST_F(WebRPCExecutorTest, HTTPErrorPath)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(requestJSON), session);
|
||||
(*handler)(std::move(requestJSON), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsErrorPath)
|
||||
TEST_F(WebRPCServerHandlerTest, WsErrorPath)
|
||||
{
|
||||
session->upgraded = true;
|
||||
static auto constexpr response = R"({
|
||||
@@ -316,12 +319,12 @@ TEST_F(WebRPCExecutorTest, WsErrorPath)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
|
||||
|
||||
(*rpcExecutor)(std::move(requestJSON), session);
|
||||
(*handler)(std::move(requestJSON), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPNotReady)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPNotReady)
|
||||
{
|
||||
static auto constexpr request = R"({
|
||||
"method": "server_info",
|
||||
@@ -344,12 +347,12 @@ TEST_F(WebRPCExecutorTest, HTTPNotReady)
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyNotReady).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsNotReady)
|
||||
TEST_F(WebRPCServerHandlerTest, WsNotReady)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
@@ -373,12 +376,12 @@ TEST_F(WebRPCExecutorTest, WsNotReady)
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyNotReady).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPInvalidAPIVersion)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPInvalidAPIVersion)
|
||||
{
|
||||
static auto constexpr request = R"({
|
||||
"method": "server_info",
|
||||
@@ -390,30 +393,17 @@ TEST_F(WebRPCExecutorTest, HTTPInvalidAPIVersion)
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"result": {
|
||||
"error": "invalid_API_version",
|
||||
"error_code": 6000,
|
||||
"error_message": "API version must be an integer",
|
||||
"status": "error",
|
||||
"type": "response",
|
||||
"request": {
|
||||
"method": "server_info",
|
||||
"params": [{
|
||||
"api_version": null
|
||||
}]
|
||||
}
|
||||
}
|
||||
})";
|
||||
static auto constexpr response = "invalid_API_version";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
EXPECT_EQ(session->message, response);
|
||||
EXPECT_EQ(session->lastStatus, boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WSInvalidAPIVersion)
|
||||
TEST_F(WebRPCServerHandlerTest, WSInvalidAPIVersion)
|
||||
{
|
||||
session->upgraded = true;
|
||||
static auto constexpr request = R"({
|
||||
@@ -438,40 +428,12 @@ TEST_F(WebRPCExecutorTest, WSInvalidAPIVersion)
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPBadSyntax)
|
||||
{
|
||||
static auto constexpr request = R"({"method2": "server_info"})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"result":{
|
||||
"error": "badSyntax",
|
||||
"error_code": 1,
|
||||
"error_message": "Method is not specified or is not a string.",
|
||||
"status": "error",
|
||||
"type": "response",
|
||||
"request": {
|
||||
"method2": "server_info",
|
||||
"params": [{}]
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPBadSyntaxWhenRequestSubscribe)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPBadSyntaxWhenRequestSubscribe)
|
||||
{
|
||||
static auto constexpr request = R"({"method": "subscribe"})";
|
||||
|
||||
@@ -494,12 +456,63 @@ TEST_F(WebRPCExecutorTest, HTTPBadSyntaxWhenRequestSubscribe)
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsBadSyntax)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPMissingCommand)
|
||||
{
|
||||
static auto constexpr request = R"({"method2": "server_info"})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = "Null method";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(session->message, response);
|
||||
EXPECT_EQ(session->lastStatus, boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPCommandNotString)
|
||||
{
|
||||
static auto constexpr request = R"({"method": 1})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = "method is not string";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(session->message, response);
|
||||
EXPECT_EQ(session->lastStatus, boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPCommandIsEmpty)
|
||||
{
|
||||
static auto constexpr request = R"({"method": ""})";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = "method is empty";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(session->message, response);
|
||||
EXPECT_EQ(session->lastStatus, boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
TEST_F(WebRPCServerHandlerTest, WsMissingCommand)
|
||||
{
|
||||
session->upgraded = true;
|
||||
static auto constexpr request = R"({
|
||||
@@ -511,8 +524,8 @@ TEST_F(WebRPCExecutorTest, WsBadSyntax)
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr response = R"({
|
||||
"error": "badSyntax",
|
||||
"error_code": 1,
|
||||
"error": "missingCommand",
|
||||
"error_code": 6001,
|
||||
"error_message": "Method/Command is not specified or is not a string.",
|
||||
"status": "error",
|
||||
"type": "response",
|
||||
@@ -525,12 +538,52 @@ TEST_F(WebRPCExecutorTest, WsBadSyntax)
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPInternalError)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPParamsUnparseableNotArray)
|
||||
{
|
||||
static auto constexpr response = "params unparseable";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr requestJSON = R"({
|
||||
"method": "ledger",
|
||||
"params": "wrong"
|
||||
})";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*handler)(std::move(requestJSON), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(session->message, response);
|
||||
EXPECT_EQ(session->lastStatus, boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPParamsUnparseableEmptyArray)
|
||||
{
|
||||
static auto constexpr response = "params unparseable";
|
||||
|
||||
mockBackendPtr->updateRange(MINSEQ); // min
|
||||
mockBackendPtr->updateRange(MAXSEQ); // max
|
||||
|
||||
static auto constexpr requestJSON = R"({
|
||||
"method": "ledger",
|
||||
"params": []
|
||||
})";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*handler)(std::move(requestJSON), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(session->message, response);
|
||||
EXPECT_EQ(session->lastStatus, boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPInternalError)
|
||||
{
|
||||
static auto constexpr response = R"({
|
||||
"result": {
|
||||
@@ -557,12 +610,12 @@ TEST_F(WebRPCExecutorTest, HTTPInternalError)
|
||||
EXPECT_CALL(*rpcEngine, notifyInternalError).Times(1);
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
|
||||
|
||||
(*rpcExecutor)(std::move(requestJSON), session);
|
||||
(*handler)(std::move(requestJSON), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsInternalError)
|
||||
TEST_F(WebRPCServerHandlerTest, WsInternalError)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
@@ -590,12 +643,12 @@ TEST_F(WebRPCExecutorTest, WsInternalError)
|
||||
EXPECT_CALL(*rpcEngine, notifyInternalError).Times(1);
|
||||
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
|
||||
|
||||
(*rpcExecutor)(std::move(requestJSON), session);
|
||||
(*handler)(std::move(requestJSON), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPOutDated)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPOutDated)
|
||||
{
|
||||
static auto constexpr request = R"({
|
||||
"method": "server_info",
|
||||
@@ -627,12 +680,12 @@ TEST_F(WebRPCExecutorTest, HTTPOutDated)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(61));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsOutdated)
|
||||
TEST_F(WebRPCServerHandlerTest, WsOutdated)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
@@ -667,17 +720,17 @@ TEST_F(WebRPCExecutorTest, WsOutdated)
|
||||
|
||||
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(61));
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
std::this_thread::sleep_for(200ms);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsTooBusy)
|
||||
TEST_F(WebRPCServerHandlerTest, WsTooBusy)
|
||||
{
|
||||
session->upgraded = true;
|
||||
|
||||
auto localRpcEngine = std::make_shared<MockRPCEngine>();
|
||||
auto localRpcExecutor = std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(
|
||||
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine, MockETLService>>(
|
||||
cfg, mockBackendPtr, localRpcEngine, etl, subManager);
|
||||
static auto constexpr request = R"({
|
||||
"command": "server_info",
|
||||
@@ -699,14 +752,14 @@ TEST_F(WebRPCExecutorTest, WsTooBusy)
|
||||
EXPECT_CALL(*localRpcEngine, notifyTooBusy).Times(1);
|
||||
EXPECT_CALL(*localRpcEngine, post).WillOnce(testing::Return(false));
|
||||
|
||||
(*localRpcExecutor)(std::move(request), session);
|
||||
(*localHandler)(std::move(request), session);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPTooBusy)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPTooBusy)
|
||||
{
|
||||
auto localRpcEngine = std::make_shared<MockRPCEngine>();
|
||||
auto localRpcExecutor = std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(
|
||||
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine, MockETLService>>(
|
||||
cfg, mockBackendPtr, localRpcEngine, etl, subManager);
|
||||
static auto constexpr request = R"({
|
||||
"method": "server_info",
|
||||
@@ -728,29 +781,23 @@ TEST_F(WebRPCExecutorTest, HTTPTooBusy)
|
||||
EXPECT_CALL(*localRpcEngine, notifyTooBusy).Times(1);
|
||||
EXPECT_CALL(*localRpcEngine, post).WillOnce(testing::Return(false));
|
||||
|
||||
(*localRpcExecutor)(std::move(request), session);
|
||||
(*localHandler)(std::move(request), session);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, HTTPRequestNotJson)
|
||||
TEST_F(WebRPCServerHandlerTest, HTTPRequestNotJson)
|
||||
{
|
||||
static auto constexpr request = "not json";
|
||||
static auto constexpr response =
|
||||
R"({
|
||||
"error": "badSyntax",
|
||||
"error_code": 1,
|
||||
"error_message": "Syntax error.",
|
||||
"status": "error",
|
||||
"type": "response"
|
||||
})";
|
||||
static auto constexpr response = "Unable to parse request: syntax error";
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
(*handler)(std::move(request), session);
|
||||
EXPECT_EQ(session->message, response);
|
||||
EXPECT_EQ(session->lastStatus, boost::beast::http::status::bad_request);
|
||||
}
|
||||
|
||||
TEST_F(WebRPCExecutorTest, WsRequestNotJson)
|
||||
TEST_F(WebRPCServerHandlerTest, WsRequestNotJson)
|
||||
{
|
||||
session->upgraded = true;
|
||||
static auto constexpr request = "not json";
|
||||
@@ -765,6 +812,6 @@ TEST_F(WebRPCExecutorTest, WsRequestNotJson)
|
||||
|
||||
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
|
||||
|
||||
(*rpcExecutor)(std::move(request), session);
|
||||
(*handler)(std::move(request), session);
|
||||
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
|
||||
}
|
||||
Reference in New Issue
Block a user