From c72db5fa5f64f0e81a938743f725133a51658c46 Mon Sep 17 00:00:00 2001 From: Tom Ritchford Date: Wed, 3 Dec 2014 15:32:20 -0500 Subject: [PATCH] Refactor away RPCHandler::doRpcCommand --- src/ripple/app/main/Main.cpp | 8 +- src/ripple/app/websocket/WSConnection.cpp | 8 +- src/ripple/net/RPCErr.h | 2 - src/ripple/net/impl/RPCErr.cpp | 13 - src/ripple/rpc/RPCHandler.h | 38 +-- src/ripple/rpc/Status.h | 32 ++- src/ripple/rpc/handlers/LedgerHeader.cpp | 1 + src/ripple/rpc/handlers/LedgerRequest.cpp | 1 + src/ripple/rpc/handlers/RipplePathFind.cpp | 1 + src/ripple/rpc/impl/Context.h | 2 +- src/ripple/rpc/impl/JsonWriter.h | 1 + src/ripple/rpc/impl/RPCHandler.cpp | 263 +++++++++++++------- src/ripple/rpc/impl/Status.cpp | 18 ++ src/ripple/server/impl/ServerHandlerImp.cpp | 71 ++++-- 14 files changed, 304 insertions(+), 155 deletions(-) diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp index a82aff5dc7..2fc6ccddea 100644 --- a/src/ripple/app/main/Main.cpp +++ b/src/ripple/app/main/Main.cpp @@ -68,10 +68,12 @@ void startServer () if (!getConfig ().QUIET) std::cerr << "Startup RPC: " << jvCommand << std::endl; - RPCHandler rhHandler (getApp().getOPs ()); - 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) std::cerr << "Result: " << jvResult << std::endl; diff --git a/src/ripple/app/websocket/WSConnection.cpp b/src/ripple/app/websocket/WSConnection.cpp index 38e267831b..3e12331478 100644 --- a/src/ripple/app/websocket/WSConnection.cpp +++ b/src/ripple/app/websocket/WSConnection.cpp @@ -159,8 +159,6 @@ Json::Value WSConnection::invokeCommand (Json::Value& jvRequest) } Resource::Charge loadType = Resource::feeReferenceRPC; - RPCHandler mRPCHandler (m_netOPs, std::dynamic_pointer_cast ( - this->shared_from_this ())); Json::Value jvResult (Json::objectValue); Role const role = port_.allow_admin ? adminRole (port_, jvRequest, @@ -172,8 +170,10 @@ Json::Value WSConnection::invokeCommand (Json::Value& jvRequest) } else { - jvResult[jss::result] = mRPCHandler.doCommand ( - jvRequest, role, loadType); + RPC::Context context { + jvRequest, loadType, m_netOPs, role, + std::dynamic_pointer_cast (this->shared_from_this ())}; + RPC::doCommand (context, jvResult[jss::result]); } getConsumer().charge (loadType); diff --git a/src/ripple/net/RPCErr.h b/src/ripple/net/RPCErr.h index ce887b5244..4f7122357e 100644 --- a/src/ripple/net/RPCErr.h +++ b/src/ripple/net/RPCErr.h @@ -22,8 +22,6 @@ namespace ripple { -Json::Value const& logRPCError (Json::Value const& json); - // VFALCO NOTE these are deprecated bool isRpcError (Json::Value jvResult); Json::Value rpcError (int iError, diff --git a/src/ripple/net/impl/RPCErr.cpp b/src/ripple/net/impl/RPCErr.cpp index d2dc3abd6c..43203687b9 100644 --- a/src/ripple/net/impl/RPCErr.cpp +++ b/src/ripple/net/impl/RPCErr.cpp @@ -34,17 +34,4 @@ bool isRpcError (Json::Value jvResult) 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 - diff --git a/src/ripple/rpc/RPCHandler.h b/src/ripple/rpc/RPCHandler.h index 3f62e5b8d9..fec3ef06d1 100644 --- a/src/ripple/rpc/RPCHandler.h +++ b/src/ripple/rpc/RPCHandler.h @@ -23,40 +23,22 @@ #include #include #include -#include +#include +#include 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 -{ -public: - explicit RPCHandler ( - NetworkOPs& netOps, InfoSub::pointer infoSub = nullptr); +/** Execute an RPC command and store the results in an std::string. */ +void executeRPC (RPC::Context&, std::string&); - using Yield = RPC::Yield; - - 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; -}; +/** Temporary flag to enable RPCs. */ +auto const streamingRPC = false; +} // RPC } // ripple #endif diff --git a/src/ripple/rpc/Status.h b/src/ripple/rpc/Status.h index 293d825d7c..ea49166483 100644 --- a/src/ripple/rpc/Status.h +++ b/src/ripple/rpc/Status.h @@ -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. If the Status is OK, the result is an empty string. */ 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. */ operator bool() const { @@ -97,16 +99,40 @@ public: return error_code_i (code_); } + /** Apply the Status to a JsonObject + */ + template + 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 { return messages_; } + /** Return the first message, if any. */ + std::string message() const; + Type type() const { 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: Type type_ = Type::none; Code code_ = OK; diff --git a/src/ripple/rpc/handlers/LedgerHeader.cpp b/src/ripple/rpc/handlers/LedgerHeader.cpp index 9e07044690..8d495a985b 100644 --- a/src/ripple/rpc/handlers/LedgerHeader.cpp +++ b/src/ripple/rpc/handlers/LedgerHeader.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/LedgerRequest.cpp b/src/ripple/rpc/handlers/LedgerRequest.cpp index dab527313f..16dae11f1e 100644 --- a/src/ripple/rpc/handlers/LedgerRequest.cpp +++ b/src/ripple/rpc/handlers/LedgerRequest.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include namespace ripple { diff --git a/src/ripple/rpc/handlers/RipplePathFind.cpp b/src/ripple/rpc/handlers/RipplePathFind.cpp index 8da1c7ac70..80317c434c 100644 --- a/src/ripple/rpc/handlers/RipplePathFind.cpp +++ b/src/ripple/rpc/handlers/RipplePathFind.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include diff --git a/src/ripple/rpc/impl/Context.h b/src/ripple/rpc/impl/Context.h index 2a03a4270b..8c0e991d72 100644 --- a/src/ripple/rpc/impl/Context.h +++ b/src/ripple/rpc/impl/Context.h @@ -33,8 +33,8 @@ struct Context Json::Value params; Resource::Charge& loadType; NetworkOPs& netOps; - InfoSub::pointer infoSub; Role role; + InfoSub::pointer infoSub; RPC::Yield yield; }; diff --git a/src/ripple/rpc/impl/JsonWriter.h b/src/ripple/rpc/impl/JsonWriter.h index b96772df2e..a87007546e 100644 --- a/src/ripple/rpc/impl/JsonWriter.h +++ b/src/ripple/rpc/impl/JsonWriter.h @@ -21,6 +21,7 @@ #define RIPPLED_RIPPLE_BASICS_TYPES_JSONWRITER_H #include +#include #include #include diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index 788ecb2de5..d26f43c567 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -25,137 +25,226 @@ #include #include #include +#include namespace ripple { +namespace RPC { -RPCHandler::RPCHandler (NetworkOPs& netOps, InfoSub::pointer infoSub) - : netOps_ (netOps) - , infoSub_ (infoSub) +namespace { + +/** + 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& result) { -} - -// 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) + if (context.role != Role::ADMIN) { // VFALCO NOTE Should we also add up the jtRPC jobs? // int jc = getApp().getJobQueue ().getJobCountGE (jtCLIENT); - if (jc > RPC::Tuning::maxJobQueueClients) + if (jc > Tuning::maxJobQueueClients) { WriteLog (lsDEBUG, RPCHandler) << "Too busy for command: " << jc; - return rpcError (rpcTOO_BUSY); + return rpcTOO_BUSY; } } - if (!params.isMember ("command")) - return rpcError (rpcCOMMAND_MISSING); + if (!context.params.isMember ("command")) + 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) << "REQUEST:" << params; + WriteLog (lsTRACE, RPCHandler) << "REQUEST:" << context.params; - role_ = role; - - auto handler = RPC::getHandler(strCommand); + auto handler = getHandler(strCommand); if (!handler) - return rpcError (rpcUNKNOWN_COMMAND); + return rpcUNKNOWN_COMMAND; - if (handler->role_ == Role::ADMIN && role_ != Role::ADMIN) - return rpcError (rpcNO_PERMISSION); + if (handler->role_ == Role::ADMIN && context.role != Role::ADMIN) + return rpcNO_PERMISSION; - if ((handler->condition_ & RPC::NEEDS_NETWORK_CONNECTION) && - (netOps_.getOperatingMode () < NetworkOPs::omSYNCING)) + if ((handler->condition_ & NEEDS_NETWORK_CONNECTION) && + (context.netOps.getOperatingMode () < NetworkOPs::omSYNCING)) { WriteLog (lsINFO, RPCHandler) << "Insufficient network mode for RPC: " - << netOps_.strOperatingMode (); + << context.netOps.strOperatingMode (); - return rpcError (rpcNO_NETWORK); + return rpcNO_NETWORK; } if (!getConfig ().RUN_STANDALONE - && (handler->condition_ & RPC::NEEDS_CURRENT_LEDGER) + && (handler->condition_ & NEEDS_CURRENT_LEDGER) && (getApp().getLedgerMaster().getValidatedLedgerAge() > - RPC::Tuning::maxValidatedLedgerAge)) + Tuning::maxValidatedLedgerAge)) { - return rpcError (rpcNO_CURRENT); + return rpcNO_CURRENT; } - if ((handler->condition_ & RPC::NEEDS_CLOSED_LEDGER) && - !netOps_.getClosedLedger ()) + if ((handler->condition_ & NEEDS_CLOSED_LEDGER) && + !context.netOps.getClosedLedger ()) { - return rpcError (rpcNO_CLOSED); + return rpcNO_CLOSED; } + result = *handler; + return rpcSUCCESS; +} + +template +Status callMethod ( + Context& context, Method method, std::string const& name, Object& result) +{ try { - LoadEvent::autoptr ev = getApp().getJobQueue().getLoadEventAP( - jtGENERIC, "cmd:" + strCommand); - RPC::Context context { - params, loadType, netOps_, infoSub_, role_, yield}; - Json::Value result (Json::objectValue); - handler->valueMethod_ (context, result); - return result; + auto v = getApp().getJobQueue().getLoadEventAP( + jtGENERIC, "cmd:" + name); + return method (context, result); } catch (std::exception& e) { WriteLog (lsINFO, RPCHandler) << "Caught throw: " << e.what (); - if (loadType == Resource::feeReferenceRPC) - loadType = Resource::feeExceptionRPC; + if (context.loadType == Resource::feeReferenceRPC) + context.loadType = Resource::feeExceptionRPC; - return rpcError (rpcINTERNAL); + inject_error (rpcINTERNAL, result); + return rpcINTERNAL; } } +template +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; + 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; + 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 diff --git a/src/ripple/rpc/impl/Status.cpp b/src/ripple/rpc/impl/Status.cpp index 0093b128ac..0cf38a814c 100644 --- a/src/ripple/rpc/impl/Status.cpp +++ b/src/ripple/rpc/impl/Status.cpp @@ -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 } // ripple diff --git a/src/ripple/server/impl/ServerHandlerImp.cpp b/src/ripple/server/impl/ServerHandlerImp.cpp index 5024934e26..75141612b0 100644 --- a/src/ripple/server/impl/ServerHandlerImp.cpp +++ b/src/ripple/server/impl/ServerHandlerImp.cpp @@ -194,8 +194,7 @@ ServerHandlerImp::onRequest (HTTP::Session& session) RPC::Coroutine::YieldFunction yieldFunction = [this, detach] (Yield const& y) { processSession (detach, y); }; - RPC::Coroutine coroutine (yieldFunction); - runCoroutine (std::move(coroutine), m_jobQueue); + runCoroutine (RPC::Coroutine (yieldFunction), m_jobQueue); } void @@ -317,14 +316,23 @@ ServerHandlerImp::processRequest ( // Parse params Json::Value params = jsonRPC ["params"]; - if (params.isNull ()) - params = Json::Value (Json::arrayValue); + if (params.isNull () || params.empty()) + params = Json::Value (Json::objectValue); - else if (!params.isArray ()) + else if (!params.isArray () || params.size() != 1) { HTTPReply (400, "params unparseable", output); return; } + else + { + params = std::move (params[0u]); + if (!params.isObject()) + { + HTTPReply (400, "params unparseable", output); + return; + } + } // VFALCO TODO Shouldn't we handle this earlier? // @@ -337,22 +345,57 @@ ServerHandlerImp::processRequest ( return; } - - RPCHandler rpcHandler (m_networkOPs); Resource::Charge loadType = Resource::feeReferenceRPC; m_journal.debug << "Query: " << strMethod << params; - auto result = rpcHandler.doRpcCommand ( - strMethod, params, role, loadType, yield); - m_journal.debug << "Reply: " << result; + // Provide the JSON-RPC method as the field "command" in the request. + params[jss::command] = strMethod; + 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); - Json::Value reply (Json::objectValue); - reply[jss::result] = std::move (result); - auto response = to_string (reply); - response += '\n'; + if (m_journal.debug.active()) + { + static const int maxSize = 10000; + if (response.size() <= maxSize) + m_journal.debug << "Reply: " << response; + else + m_journal.debug << "Reply: " << response.substr (0, maxSize); + } HTTPReply (200, response, output); }