Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/server.cpp
Show All 13 Lines | |||||
#include "ui_interface.h" | #include "ui_interface.h" | ||||
#include "util.h" | #include "util.h" | ||||
#include "utilstrencodings.h" | #include "utilstrencodings.h" | ||||
#include <univalue.h> | #include <univalue.h> | ||||
#include <boost/algorithm/string/case_conv.hpp> // for to_upper() | #include <boost/algorithm/string/case_conv.hpp> // for to_upper() | ||||
#include <boost/bind.hpp> | #include <boost/bind.hpp> | ||||
#include <boost/range/adaptor/map.hpp> | |||||
#include <boost/range/adaptor/transformed.hpp> | |||||
#include <boost/range/algorithm/copy.hpp> | |||||
#include <boost/signals2/signal.hpp> | #include <boost/signals2/signal.hpp> | ||||
#include <boost/thread.hpp> | #include <boost/thread.hpp> | ||||
#include <memory> // for unique_ptr | #include <memory> // for unique_ptr | ||||
#include <set> | #include <set> | ||||
#include <unordered_map> | #include <unordered_map> | ||||
static bool fRPCRunning = false; | static bool fRPCRunning = false; | ||||
static bool fRPCInWarmup = true; | static bool fRPCInWarmup = true; | ||||
static std::string rpcWarmupStatus("RPC server started"); | static std::string rpcWarmupStatus("RPC server started"); | ||||
static CCriticalSection cs_rpcWarmup; | static CCriticalSection cs_rpcWarmup; | ||||
/* Timer-creating functions */ | /* Timer-creating functions */ | ||||
static RPCTimerInterface *timerInterface = nullptr; | static RPCTimerInterface *timerInterface = nullptr; | ||||
/* Map of name to timer. */ | /* Map of name to timer. */ | ||||
static std::map<std::string, std::unique_ptr<RPCTimerBase>> deadlineTimers; | static std::map<std::string, std::unique_ptr<RPCTimerBase>> deadlineTimers; | ||||
const RPCCommand *RPCServer::operator[](const std::string &name) const { | |||||
auto iter = commands.find(name); | |||||
if (iter == commands.end()) { | |||||
return nullptr; | |||||
} | |||||
return (*iter).second; | |||||
} | |||||
bool RPCServer::RegisterCommand(const RPCCommand *command) { | |||||
// Exit immediately if the RPC server is already running | |||||
if (IsRPCRunning()) { | |||||
return false; | |||||
} | |||||
// Do not allow commands to be overwritten | |||||
std::string commandNameStr = command->GetName().GetNameStr(); | |||||
if (commands.find(commandNameStr) == commands.end()) { | |||||
return false; | |||||
} | |||||
commands[commandNameStr] = command; | |||||
return true; | |||||
} | |||||
/** | |||||
* Process named arguments into a vector of positional arguments, based on the | |||||
* passed-in specification for the RPC call's arguments. | |||||
*/ | |||||
static inline JSONRPCRequest | |||||
jasonbcox: Note that this function (transformNamedArguments) was moved within this file without changes… | |||||
transformNamedArguments(const JSONRPCRequest &in, | |||||
const std::vector<std::string> &argNames) { | |||||
JSONRPCRequest out = in; | |||||
out.params = UniValue(UniValue::VARR); | |||||
// Build a map of parameters, and remove ones that have been processed, so | |||||
// that we can throw a focused error if there is an unknown one. | |||||
const std::vector<std::string> &keys = in.params.getKeys(); | |||||
const std::vector<UniValue> &values = in.params.getValues(); | |||||
std::unordered_map<std::string, const UniValue *> argsIn; | |||||
for (size_t i = 0; i < keys.size(); ++i) { | |||||
argsIn[keys[i]] = &values[i]; | |||||
} | |||||
// Process expected parameters. | |||||
int hole = 0; | |||||
for (const std::string &argName : argNames) { | |||||
auto fr = argsIn.find(argName); | |||||
if (fr != argsIn.end()) { | |||||
for (int i = 0; i < hole; ++i) { | |||||
// Fill hole between specified parameters with JSON nulls, but | |||||
// not at the end (for backwards compatibility with calls that | |||||
// act based on number of specified parameters). | |||||
out.params.push_back(UniValue()); | |||||
} | |||||
hole = 0; | |||||
out.params.push_back(*fr->second); | |||||
argsIn.erase(fr); | |||||
} else { | |||||
hole += 1; | |||||
} | |||||
} | |||||
// If there are still arguments in the argsIn map, this is an error. | |||||
if (!argsIn.empty()) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"Unknown named parameter " + argsIn.begin()->first); | |||||
} | |||||
// Return request with named arguments transformed to positional arguments | |||||
return out; | |||||
} | |||||
static std::string GetParameterName(const RPCParameter *param) { | |||||
return param->GetName(); | |||||
} | |||||
UniValue RPCServer::ExecuteCommand(Config &config, | |||||
const JSONRPCRequest &request) const { | |||||
// Return immediately if in warmup | |||||
{ | |||||
LOCK(cs_rpcWarmup); | |||||
if (fRPCInWarmup) { | |||||
throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); | |||||
} | |||||
} | |||||
// Check if context-sensitive RPC method is valid | |||||
auto iter = commands.find(request.strMethod); | |||||
if (iter == commands.end()) { | |||||
// Check if context-free RPC method is valid and execute it | |||||
return tableRPC.execute(config, request); | |||||
// TODO: Once migrated away from tableRPC, remove the above and | |||||
// uncomment the line below. | |||||
// throw JSONRPCError(RPC_METHOD_NOT_FOUND, "RPC command not found"); | |||||
} | |||||
const RPCCommand *command = commands.at(request.strMethod); | |||||
try { | |||||
// Execute, convert arguments to array if necessary | |||||
if (request.params.isObject()) { | |||||
std::vector<std::string> paramNames; | |||||
boost::copy(command->GetParameters() | | |||||
boost::adaptors::transformed(GetParameterName), | |||||
std::back_inserter(paramNames)); | |||||
return command->Execute( | |||||
transformNamedArguments(request, paramNames)); | |||||
} else { | |||||
return command->Execute(request); | |||||
} | |||||
} catch (const std::exception &e) { | |||||
throw JSONRPCError(RPC_MISC_ERROR, e.what()); | |||||
} | |||||
} | |||||
std::string RPCServer::Help(Config &config, const RPCCommandName &name, | |||||
const JSONRPCRequest &helpRequest) const { | |||||
// TODO | |||||
return ""; | |||||
} | |||||
std::vector<std::string> RPCServer::ListCommandNames() const { | |||||
std::vector<std::string> commandNames; | |||||
boost::copy(commands | boost::adaptors::map_keys, | |||||
std::back_inserter(commandNames)); | |||||
return commandNames; | |||||
} | |||||
static struct CRPCSignals { | static struct CRPCSignals { | ||||
boost::signals2::signal<void()> Started; | boost::signals2::signal<void()> Started; | ||||
boost::signals2::signal<void()> Stopped; | boost::signals2::signal<void()> Stopped; | ||||
boost::signals2::signal<void(const ContextFreeRPCCommand &)> PreCommand; | boost::signals2::signal<void(const ContextFreeRPCCommand &)> PreCommand; | ||||
boost::signals2::signal<void(const ContextFreeRPCCommand &)> PostCommand; | boost::signals2::signal<void(const ContextFreeRPCCommand &)> PostCommand; | ||||
} g_rpcSignals; | } g_rpcSignals; | ||||
void RPCServerSignals::OnStarted(std::function<void()> slot) { | void RPCServerSignals::OnStarted(std::function<void()> slot) { | ||||
▲ Show 20 Lines • Show All 223 Lines • ▼ Show 20 Lines | if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { | ||||
"\"text\" (string) The help text\n"); | "\"text\" (string) The help text\n"); | ||||
} | } | ||||
std::string strCommand; | std::string strCommand; | ||||
if (jsonRequest.params.size() > 0) { | if (jsonRequest.params.size() > 0) { | ||||
strCommand = jsonRequest.params[0].get_str(); | strCommand = jsonRequest.params[0].get_str(); | ||||
} | } | ||||
// TODO: Add RPCServer help call until this help() function is migrated to | |||||
// the new RPC classes. | |||||
return tableRPC.help(config, strCommand, jsonRequest); | return tableRPC.help(config, strCommand, jsonRequest); | ||||
} | } | ||||
static UniValue stop(const Config &config, const JSONRPCRequest &jsonRequest) { | static UniValue stop(const Config &config, const JSONRPCRequest &jsonRequest) { | ||||
// Accept the deprecated and ignored 'detach' boolean argument | // Accept the deprecated and ignored 'detach' boolean argument | ||||
if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { | if (jsonRequest.fHelp || jsonRequest.params.size() > 1) { | ||||
throw std::runtime_error("stop\n" | throw std::runtime_error("stop\n" | ||||
"\nStop Bitcoin server."); | "\nStop Bitcoin server."); | ||||
▲ Show 20 Lines • Show All 141 Lines • ▼ Show 20 Lines | std::string JSONRPCExecBatch(Config &config, const JSONRPCRequest &jreq, | ||||
UniValue ret(UniValue::VARR); | UniValue ret(UniValue::VARR); | ||||
for (size_t i = 0; i < vReq.size(); i++) { | for (size_t i = 0; i < vReq.size(); i++) { | ||||
ret.push_back(JSONRPCExecOne(config, jreq, vReq[i])); | ret.push_back(JSONRPCExecOne(config, jreq, vReq[i])); | ||||
} | } | ||||
return ret.write() + "\n"; | return ret.write() + "\n"; | ||||
} | } | ||||
/** | |||||
* Process named arguments into a vector of positional arguments, based on the | |||||
* passed-in specification for the RPC call's arguments. | |||||
*/ | |||||
static inline JSONRPCRequest | |||||
transformNamedArguments(const JSONRPCRequest &in, | |||||
const std::vector<std::string> &argNames) { | |||||
JSONRPCRequest out = in; | |||||
out.params = UniValue(UniValue::VARR); | |||||
// Build a map of parameters, and remove ones that have been processed, so | |||||
// that we can throw a focused error if there is an unknown one. | |||||
const std::vector<std::string> &keys = in.params.getKeys(); | |||||
const std::vector<UniValue> &values = in.params.getValues(); | |||||
std::unordered_map<std::string, const UniValue *> argsIn; | |||||
for (size_t i = 0; i < keys.size(); ++i) { | |||||
argsIn[keys[i]] = &values[i]; | |||||
} | |||||
// Process expected parameters. | |||||
int hole = 0; | |||||
for (const std::string &argName : argNames) { | |||||
auto fr = argsIn.find(argName); | |||||
if (fr != argsIn.end()) { | |||||
for (int i = 0; i < hole; ++i) { | |||||
// Fill hole between specified parameters with JSON nulls, but | |||||
// not at the end (for backwards compatibility with calls that | |||||
// act based on number of specified parameters). | |||||
out.params.push_back(UniValue()); | |||||
} | |||||
hole = 0; | |||||
out.params.push_back(*fr->second); | |||||
argsIn.erase(fr); | |||||
} else { | |||||
hole += 1; | |||||
} | |||||
} | |||||
// If there are still arguments in the argsIn map, this is an error. | |||||
if (!argsIn.empty()) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"Unknown named parameter " + argsIn.begin()->first); | |||||
} | |||||
// Return request with named arguments transformed to positional arguments | |||||
return out; | |||||
} | |||||
UniValue CRPCTable::execute(Config &config, | UniValue CRPCTable::execute(Config &config, | ||||
const JSONRPCRequest &request) const { | const JSONRPCRequest &request) const { | ||||
// Return immediately if in warmup | // Return immediately if in warmup | ||||
{ | { | ||||
LOCK(cs_rpcWarmup); | LOCK(cs_rpcWarmup); | ||||
if (fRPCInWarmup) { | if (fRPCInWarmup) { | ||||
throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); | throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); | ||||
} | } | ||||
} | } | ||||
// Find method | // Check if legacy RPC method is valid. | ||||
// See RPCServer::ExecuteCommand for context-sensitive RPC commands. | |||||
const ContextFreeRPCCommand *pcmd = tableRPC[request.strMethod]; | const ContextFreeRPCCommand *pcmd = tableRPC[request.strMethod]; | ||||
if (!pcmd) { | if (!pcmd) { | ||||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); | ||||
} | } | ||||
g_rpcSignals.PreCommand(*pcmd); | g_rpcSignals.PreCommand(*pcmd); | ||||
try { | try { | ||||
▲ Show 20 Lines • Show All 73 Lines • Show Last 20 Lines |
Note that this function (transformNamedArguments) was moved within this file without changes because RPCServer has it as a dependency. No changes were made to this function.