Refactor away RPCHandler::doRpcCommand

This commit is contained in:
Tom Ritchford
2014-12-03 15:32:20 -05:00
committed by Vinnie Falco
parent fc9a23d6d4
commit c72db5fa5f
14 changed files with 304 additions and 155 deletions

View File

@@ -68,10 +68,12 @@ void startServer ()
if (!getConfig ().QUIET) if (!getConfig ().QUIET)
std::cerr << "Startup RPC: " << jvCommand << std::endl; std::cerr << "Startup RPC: " << jvCommand << std::endl;
RPCHandler rhHandler (getApp().getOPs ());
Resource::Charge loadType = Resource::feeReferenceRPC; Resource::Charge loadType = Resource::feeReferenceRPC;
Json::Value jvResult = rhHandler.doCommand (jvCommand, Role::ADMIN, loadType); RPC::Context context {
jvCommand, loadType, getApp().getOPs (), Role::ADMIN};
Json::Value jvResult;
RPC::doCommand (context, jvResult);
if (!getConfig ().QUIET) if (!getConfig ().QUIET)
std::cerr << "Result: " << jvResult << std::endl; std::cerr << "Result: " << jvResult << std::endl;

View File

@@ -159,8 +159,6 @@ Json::Value WSConnection::invokeCommand (Json::Value& jvRequest)
} }
Resource::Charge loadType = Resource::feeReferenceRPC; Resource::Charge loadType = Resource::feeReferenceRPC;
RPCHandler mRPCHandler (m_netOPs, std::dynamic_pointer_cast<InfoSub> (
this->shared_from_this ()));
Json::Value jvResult (Json::objectValue); Json::Value jvResult (Json::objectValue);
Role const role = port_.allow_admin ? adminRole (port_, jvRequest, Role const role = port_.allow_admin ? adminRole (port_, jvRequest,
@@ -172,8 +170,10 @@ Json::Value WSConnection::invokeCommand (Json::Value& jvRequest)
} }
else else
{ {
jvResult[jss::result] = mRPCHandler.doCommand ( RPC::Context context {
jvRequest, role, loadType); jvRequest, loadType, m_netOPs, role,
std::dynamic_pointer_cast<InfoSub> (this->shared_from_this ())};
RPC::doCommand (context, jvResult[jss::result]);
} }
getConsumer().charge (loadType); getConsumer().charge (loadType);

View File

@@ -22,8 +22,6 @@
namespace ripple { namespace ripple {
Json::Value const& logRPCError (Json::Value const& json);
// VFALCO NOTE these are deprecated // VFALCO NOTE these are deprecated
bool isRpcError (Json::Value jvResult); bool isRpcError (Json::Value jvResult);
Json::Value rpcError (int iError, Json::Value rpcError (int iError,

View File

@@ -34,17 +34,4 @@ bool isRpcError (Json::Value jvResult)
return jvResult.isObject () && jvResult.isMember ("error"); return jvResult.isObject () && jvResult.isMember ("error");
} }
Json::Value const& logRPCError (Json::Value const& json)
{
if (RPC::contains_error (json))
{
WriteLog (lsDEBUG, RPCErr) <<
"rpcError: " << json ["error"] <<
": " << json ["error_message"];
}
return json;
}
} // ripple } // ripple

View File

@@ -23,40 +23,22 @@
#include <ripple/server/Role.h> #include <ripple/server/Role.h>
#include <ripple/core/Config.h> #include <ripple/core/Config.h>
#include <ripple/net/InfoSub.h> #include <ripple/net/InfoSub.h>
#include <ripple/rpc/Yield.h> #include <ripple/rpc/impl/Context.h>
#include <ripple/rpc/Status.h>
namespace ripple { namespace ripple {
namespace RPC {
class NetworkOPs; /** Execute an RPC command and store the results in a Json::Value. */
Status doCommand (RPC::Context&, Json::Value&);
class RPCHandler /** Execute an RPC command and store the results in an std::string. */
{ void executeRPC (RPC::Context&, std::string&);
public:
explicit RPCHandler (
NetworkOPs& netOps, InfoSub::pointer infoSub = nullptr);
using Yield = RPC::Yield; /** Temporary flag to enable RPCs. */
auto const streamingRPC = false;
Json::Value doCommand (
Json::Value const& request,
Role role,
Resource::Charge& loadType,
Yield yield = {});
Json::Value doRpcCommand (
std::string const& command,
Json::Value const& params,
Role role,
Resource::Charge& loadType,
Yield yield = {});
private:
NetworkOPs& netOps_;
InfoSub::pointer infoSub_;
Role role_ = Role::FORBID;
};
} // RPC
} // ripple } // ripple
#endif #endif

View File

@@ -61,14 +61,16 @@ public:
{ {
} }
Status (error_code_i e, std::string const& s)
: type_ (Type::error_code_i), code_ (e), messages_ ({s})
{
}
/* Returns a representation of the integer status Code as a string. /* Returns a representation of the integer status Code as a string.
If the Status is OK, the result is an empty string. If the Status is OK, the result is an empty string.
*/ */
std::string codeString () const; std::string codeString () const;
/** Fill a Json::Value. If the Status is OK, fillJson has no effect. */
void fillJson(Json::Value&);
/** Returns true if the Status is *not* OK. */ /** Returns true if the Status is *not* OK. */
operator bool() const operator bool() const
{ {
@@ -97,16 +99,40 @@ public:
return error_code_i (code_); return error_code_i (code_);
} }
/** Apply the Status to a JsonObject
*/
template <class Object>
void inject (Object& object)
{
if (auto ec = toErrorCode())
{
if (messages_.empty())
inject_error (ec, object);
else
inject_error (ec, message(), object);
}
}
Strings const& messages() const Strings const& messages() const
{ {
return messages_; return messages_;
} }
/** Return the first message, if any. */
std::string message() const;
Type type() const Type type() const
{ {
return type_; return type_;
} }
std::string toString() const;
/** Fill a Json::Value with an RPC 2.0 response.
If the Status is OK, fillJson has no effect.
Not currently used. */
void fillJson(Json::Value&);
private: private:
Type type_ = Type::none; Type type_ = Type::none;
Code code_ = OK; Code code_ = OK;

View File

@@ -17,6 +17,7 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/LedgerToJson.h>
namespace ripple { namespace ripple {

View File

@@ -17,6 +17,7 @@
*/ */
//============================================================================== //==============================================================================
#include <ripple/app/ledger/LedgerToJson.h>
namespace ripple { namespace ripple {

View File

@@ -19,6 +19,7 @@
#include <ripple/app/paths/AccountCurrencies.h> #include <ripple/app/paths/AccountCurrencies.h>
#include <ripple/app/paths/FindPaths.h> #include <ripple/app/paths/FindPaths.h>
#include <ripple/core/LoadFeeTrack.h>
#include <ripple/protocol/STParsedJSON.h> #include <ripple/protocol/STParsedJSON.h>
#include <ripple/rpc/impl/LegacyPathFind.h> #include <ripple/rpc/impl/LegacyPathFind.h>
#include <ripple/server/Role.h> #include <ripple/server/Role.h>

View File

@@ -33,8 +33,8 @@ struct Context
Json::Value params; Json::Value params;
Resource::Charge& loadType; Resource::Charge& loadType;
NetworkOPs& netOps; NetworkOPs& netOps;
InfoSub::pointer infoSub;
Role role; Role role;
InfoSub::pointer infoSub;
RPC::Yield yield; RPC::Yield yield;
}; };

View File

@@ -21,6 +21,7 @@
#define RIPPLED_RIPPLE_BASICS_TYPES_JSONWRITER_H #define RIPPLED_RIPPLE_BASICS_TYPES_JSONWRITER_H
#include <ripple/basics/ToString.h> #include <ripple/basics/ToString.h>
#include <ripple/rpc/ErrorCodes.h>
#include <ripple/rpc/Output.h> #include <ripple/rpc/Output.h>
#include <ripple/rpc/ErrorCodes.h> #include <ripple/rpc/ErrorCodes.h>

View File

@@ -25,137 +25,226 @@
#include <ripple/rpc/impl/Tuning.h> #include <ripple/rpc/impl/Tuning.h>
#include <ripple/rpc/impl/Context.h> #include <ripple/rpc/impl/Context.h>
#include <ripple/rpc/impl/Handler.h> #include <ripple/rpc/impl/Handler.h>
#include <ripple/rpc/impl/WriteJson.h>
namespace ripple { namespace ripple {
namespace RPC {
RPCHandler::RPCHandler (NetworkOPs& netOps, InfoSub::pointer infoSub) namespace {
: netOps_ (netOps)
, infoSub_ (infoSub) /**
This code is called from both the HTTP RPC handler and Websockets.
The form of the Json returned is somewhat different between the two services.
HTML:
Success:
{
"result" : {
"ledger" : {
"accepted" : false,
"transaction_hash" : "..."
},
"ledger_index" : 10300865,
"validated" : false,
"status" : "success" # Status is inside the result.
}
}
Failure:
{
"result" : {
"error" : "noNetwork",
"error_code" : 16,
"error_message" : "Not synced to Ripple network.",
"request" : {
"command" : "ledger",
"ledger_index" : 10300865
},
"status" : "error"
}
}
Websocket:
Success:
{
"result" : {
"ledger" : {
"accepted" : false,
"transaction_hash" : "..."
},
"ledger_index" : 10300865,
"validated" : false
}
"type": "response",
"status": "success", # Status is OUTside the result!
"id": "client's ID", # Optional
"warning": 3.14 # Optional
}
Failure:
{
"error" : "noNetwork",
"error_code" : 16,
"error_message" : "Not synced to Ripple network.",
"request" : {
"command" : "ledger",
"ledger_index" : 10300865
},
"type": "response",
"status" : "error",
"id": "client's ID" # Optional
}
*/
error_code_i fillHandler (Context& context,
boost::optional<Handler const&>& result)
{ {
} if (context.role != Role::ADMIN)
// Provide the JSON-RPC "result" value.
//
// JSON-RPC provides a method and an array of params. JSON-RPC is used as a
// transport for a command and a request object. The command is the method. The
// request object is supplied as the first element of the params.
Json::Value RPCHandler::doRpcCommand (
const std::string& strMethod,
Json::Value const& jvParams,
Role role,
Resource::Charge& loadType,
Yield yield)
{
WriteLog (lsTRACE, RPCHandler)
<< "doRpcCommand:" << strMethod << ":" << jvParams;
if (!jvParams.isArray () || jvParams.size () > 1)
return logRPCError (rpcError (rpcINVALID_PARAMS));
Json::Value params = jvParams.size () ? jvParams[0u]
: Json::Value (Json::objectValue);
if (!params.isObject ())
return logRPCError (rpcError (rpcINVALID_PARAMS));
// Provide the JSON-RPC method as the field "command" in the request.
params[jss::command] = strMethod;
Json::Value jvResult = doCommand (params, role, loadType, yield);
// Always report "status". On an error report the request as received.
if (jvResult.isMember ("error"))
{
jvResult[jss::status] = jss::error;
jvResult[jss::request] = params;
}
else
{
jvResult[jss::status] = jss::success;
}
return logRPCError (jvResult);
}
Json::Value RPCHandler::doCommand (
const Json::Value& params,
Role role,
Resource::Charge& loadType,
Yield yield)
{
if (role != Role::ADMIN)
{ {
// VFALCO NOTE Should we also add up the jtRPC jobs? // VFALCO NOTE Should we also add up the jtRPC jobs?
// //
int jc = getApp().getJobQueue ().getJobCountGE (jtCLIENT); int jc = getApp().getJobQueue ().getJobCountGE (jtCLIENT);
if (jc > RPC::Tuning::maxJobQueueClients) if (jc > Tuning::maxJobQueueClients)
{ {
WriteLog (lsDEBUG, RPCHandler) << "Too busy for command: " << jc; WriteLog (lsDEBUG, RPCHandler) << "Too busy for command: " << jc;
return rpcError (rpcTOO_BUSY); return rpcTOO_BUSY;
} }
} }
if (!params.isMember ("command")) if (!context.params.isMember ("command"))
return rpcError (rpcCOMMAND_MISSING); return rpcCOMMAND_MISSING;
std::string strCommand = params[jss::command].asString (); std::string strCommand = context.params[jss::command].asString ();
WriteLog (lsTRACE, RPCHandler) << "COMMAND:" << strCommand; WriteLog (lsTRACE, RPCHandler) << "COMMAND:" << strCommand;
WriteLog (lsTRACE, RPCHandler) << "REQUEST:" << params; WriteLog (lsTRACE, RPCHandler) << "REQUEST:" << context.params;
role_ = role; auto handler = getHandler(strCommand);
auto handler = RPC::getHandler(strCommand);
if (!handler) if (!handler)
return rpcError (rpcUNKNOWN_COMMAND); return rpcUNKNOWN_COMMAND;
if (handler->role_ == Role::ADMIN && role_ != Role::ADMIN) if (handler->role_ == Role::ADMIN && context.role != Role::ADMIN)
return rpcError (rpcNO_PERMISSION); return rpcNO_PERMISSION;
if ((handler->condition_ & RPC::NEEDS_NETWORK_CONNECTION) && if ((handler->condition_ & NEEDS_NETWORK_CONNECTION) &&
(netOps_.getOperatingMode () < NetworkOPs::omSYNCING)) (context.netOps.getOperatingMode () < NetworkOPs::omSYNCING))
{ {
WriteLog (lsINFO, RPCHandler) WriteLog (lsINFO, RPCHandler)
<< "Insufficient network mode for RPC: " << "Insufficient network mode for RPC: "
<< netOps_.strOperatingMode (); << context.netOps.strOperatingMode ();
return rpcError (rpcNO_NETWORK); return rpcNO_NETWORK;
} }
if (!getConfig ().RUN_STANDALONE if (!getConfig ().RUN_STANDALONE
&& (handler->condition_ & RPC::NEEDS_CURRENT_LEDGER) && (handler->condition_ & NEEDS_CURRENT_LEDGER)
&& (getApp().getLedgerMaster().getValidatedLedgerAge() > && (getApp().getLedgerMaster().getValidatedLedgerAge() >
RPC::Tuning::maxValidatedLedgerAge)) Tuning::maxValidatedLedgerAge))
{ {
return rpcError (rpcNO_CURRENT); return rpcNO_CURRENT;
} }
if ((handler->condition_ & RPC::NEEDS_CLOSED_LEDGER) && if ((handler->condition_ & NEEDS_CLOSED_LEDGER) &&
!netOps_.getClosedLedger ()) !context.netOps.getClosedLedger ())
{ {
return rpcError (rpcNO_CLOSED); return rpcNO_CLOSED;
} }
result = *handler;
return rpcSUCCESS;
}
template <class Object, class Method>
Status callMethod (
Context& context, Method method, std::string const& name, Object& result)
{
try try
{ {
LoadEvent::autoptr ev = getApp().getJobQueue().getLoadEventAP( auto v = getApp().getJobQueue().getLoadEventAP(
jtGENERIC, "cmd:" + strCommand); jtGENERIC, "cmd:" + name);
RPC::Context context { return method (context, result);
params, loadType, netOps_, infoSub_, role_, yield};
Json::Value result (Json::objectValue);
handler->valueMethod_ (context, result);
return result;
} }
catch (std::exception& e) catch (std::exception& e)
{ {
WriteLog (lsINFO, RPCHandler) << "Caught throw: " << e.what (); WriteLog (lsINFO, RPCHandler) << "Caught throw: " << e.what ();
if (loadType == Resource::feeReferenceRPC) if (context.loadType == Resource::feeReferenceRPC)
loadType = Resource::feeExceptionRPC; context.loadType = Resource::feeExceptionRPC;
return rpcError (rpcINTERNAL); inject_error (rpcINTERNAL, result);
return rpcINTERNAL;
} }
} }
template <class Method, class Object>
void getResult (
Context& context, Method method, Object& object, std::string const& name)
{
auto&& result = addObject (object, jss::result);
if (auto status = callMethod (context, method, name, result))
{
WriteLog (lsDEBUG, RPCErr) << "rpcError: " << status.toString();
result[jss::status] = jss::error;
result[jss::request] = context.params;
}
else
{
result[jss::status] = jss::success;
}
}
} // namespace
Status doCommand (RPC::Context& context, Json::Value& result)
{
boost::optional <Handler const&> handler;
if (auto error = fillHandler (context, handler))
{
inject_error (error, result);
return error;
}
if (auto method = handler->valueMethod_)
return callMethod (context, method, handler->name_, result);
return rpcUNKNOWN_COMMAND;
}
/** Execute an RPC command and store the results in a string. */
void executeRPC (RPC::Context& context, std::string& output)
{
boost::optional <Handler const&> handler;
if (auto error = fillHandler (context, handler))
{
auto wo = stringWriterObject (output);
auto&& sub = addObject (*wo, jss::result);
inject_error (error, sub);
}
else if (auto method = handler->objectMethod_)
{
auto wo = stringWriterObject (output);
getResult (context, method, *wo, handler->name_);
}
else if (auto method = handler->valueMethod_)
{
auto object = Json::Value (Json::objectValue);
getResult (context, method, object, handler->name_);
if (streamingRPC)
output = jsonAsString (object);
else
output = to_string (object);
}
else
{
// Can't ever get here.
assert (false);
throw RPC::JsonException ("RPC handler with no method");
}
}
} // RPC
} // ripple } // ripple

View File

@@ -71,5 +71,23 @@ void Status::fillJson (Json::Value& value)
} }
} }
std::string Status::message() const {
std::string result;
for (auto& m: messages_)
{
if (!result.empty())
result += '/';
result += m;
}
return result;
}
std::string Status::toString() const {
if (*this)
return codeString() + ":" + message();
return "";
}
} // namespace RPC } // namespace RPC
} // ripple } // ripple

View File

@@ -194,8 +194,7 @@ ServerHandlerImp::onRequest (HTTP::Session& session)
RPC::Coroutine::YieldFunction yieldFunction = RPC::Coroutine::YieldFunction yieldFunction =
[this, detach] (Yield const& y) { processSession (detach, y); }; [this, detach] (Yield const& y) { processSession (detach, y); };
RPC::Coroutine coroutine (yieldFunction); runCoroutine (RPC::Coroutine (yieldFunction), m_jobQueue);
runCoroutine (std::move(coroutine), m_jobQueue);
} }
void void
@@ -317,14 +316,23 @@ ServerHandlerImp::processRequest (
// Parse params // Parse params
Json::Value params = jsonRPC ["params"]; Json::Value params = jsonRPC ["params"];
if (params.isNull ()) if (params.isNull () || params.empty())
params = Json::Value (Json::arrayValue); params = Json::Value (Json::objectValue);
else if (!params.isArray ()) else if (!params.isArray () || params.size() != 1)
{ {
HTTPReply (400, "params unparseable", output); HTTPReply (400, "params unparseable", output);
return; return;
} }
else
{
params = std::move (params[0u]);
if (!params.isObject())
{
HTTPReply (400, "params unparseable", output);
return;
}
}
// VFALCO TODO Shouldn't we handle this earlier? // VFALCO TODO Shouldn't we handle this earlier?
// //
@@ -337,22 +345,57 @@ ServerHandlerImp::processRequest (
return; return;
} }
RPCHandler rpcHandler (m_networkOPs);
Resource::Charge loadType = Resource::feeReferenceRPC; Resource::Charge loadType = Resource::feeReferenceRPC;
m_journal.debug << "Query: " << strMethod << params; m_journal.debug << "Query: " << strMethod << params;
auto result = rpcHandler.doRpcCommand ( // Provide the JSON-RPC method as the field "command" in the request.
strMethod, params, role, loadType, yield); params[jss::command] = strMethod;
m_journal.debug << "Reply: " << result; WriteLog (lsTRACE, RPCHandler)
<< "doRpcCommand:" << strMethod << ":" << params;
RPC::Context context {params, loadType, m_networkOPs, role, nullptr, yield};
std::string response;
if (RPC::streamingRPC)
{
executeRPC (context, response);
}
else
{
Json::Value result;
RPC::doCommand (context, result);
// Always report "status". On an error report the request as received.
if (result.isMember ("error"))
{
result[jss::status] = jss::error;
result[jss::request] = params;
WriteLog (lsDEBUG, RPCErr) <<
"rpcError: " << result ["error"] <<
": " << result ["error_message"];
}
else
{
result[jss::status] = jss::success;
}
Json::Value reply (Json::objectValue);
reply[jss::result] = std::move (result);
response = to_string (reply);
}
response += '\n';
usage.charge (loadType); usage.charge (loadType);
Json::Value reply (Json::objectValue); if (m_journal.debug.active())
reply[jss::result] = std::move (result); {
auto response = to_string (reply); static const int maxSize = 10000;
response += '\n'; if (response.size() <= maxSize)
m_journal.debug << "Reply: " << response;
else
m_journal.debug << "Reply: " << response.substr (0, maxSize);
}
HTTPReply (200, response, output); HTTPReply (200, response, output);
} }