diff --git a/package.json b/package.json index 00f345360..11ee2d9db 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "dependencies": { "async": "~0.1.22", "ws": "~0.4.22", - "extend": "~1.1.1" + "extend": "~1.1.1", + "simple-jsonrpc": "~0.0.1" }, "devDependencies": { "buster": "~0.6.2", diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index 6cf4ffa9a..4b5b46816 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -537,6 +537,7 @@ int commandLineRPC(const std::vector& vCmd) theConfig.RPC_PORT, theConfig.RPC_USER, theConfig.RPC_PASSWORD, + "", jvRequest.isMember("method") // Allow parser to rewrite method. ? jvRequest["method"].asString() : vCmd[0], @@ -597,11 +598,11 @@ int commandLineRPC(const std::vector& vCmd) return nRet; } -Json::Value callRPC(const std::string& strIp, const int iPort, const std::string& strUsername, const std::string& strPassword, const std::string& strMethod, const Json::Value& params) +Json::Value callRPC(const std::string& strIp, const int iPort, const std::string& strUsername, const std::string& strPassword, const std::string& strPath, const std::string& strMethod, const Json::Value& params) { // Connect to localhost if (!theConfig.QUIET) - std::cerr << "Connecting to: " << theConfig.RPC_IP << ":" << theConfig.RPC_PORT << std::endl; + std::cerr << "Connecting to: " << strIp << ":" << iPort << std::endl; boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(strIp), iPort); @@ -618,7 +619,7 @@ Json::Value callRPC(const std::string& strIp, const int iPort, const std::string // Send request std::string strRequest = JSONRPCRequest(strMethod, params, Json::Value(1)); cLog(lsDEBUG) << "send request " << strMethod << " : " << strRequest << std::endl; - std::string strPost = createHTTPPost(strRequest, mapRequestHeaders); + std::string strPost = createHTTPPost(strPath, strRequest, mapRequestHeaders); stream << strPost << std::flush; // std::cerr << "post " << strPost << std::endl; diff --git a/src/cpp/ripple/CallRPC.h b/src/cpp/ripple/CallRPC.h index 9b9ca8396..18f25eedb 100644 --- a/src/cpp/ripple/CallRPC.h +++ b/src/cpp/ripple/CallRPC.h @@ -41,7 +41,7 @@ public: }; extern int commandLineRPC(const std::vector& vCmd); -extern Json::Value callRPC(const std::string& strIp, const int iPort, const std::string& strUsername, const std::string& strPassword, const std::string& strMethod, const Json::Value& params); +extern Json::Value callRPC(const std::string& strIp, const int iPort, const std::string& strUsername, const std::string& strPassword, const std::string& strPath, const std::string& strMethod, const Json::Value& params); #endif diff --git a/src/cpp/ripple/RPC.h b/src/cpp/ripple/RPC.h index 7d0406a20..61d42a6be 100644 --- a/src/cpp/ripple/RPC.h +++ b/src/cpp/ripple/RPC.h @@ -29,7 +29,7 @@ enum http_status_type extern std::string JSONRPCRequest(const std::string& strMethod, const Json::Value& params, const Json::Value& id); -extern std::string createHTTPPost(const std::string& strMsg, +extern std::string createHTTPPost(const std::string& strPath, const std::string& strMsg, const std::map& mapRequestHeaders); extern int ReadHTTP(std::basic_istream& stream, diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index eda9b97b4..123400760 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -2370,12 +2370,12 @@ Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest) // 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& jvParams, int iRole) { - // cLog(lsTRACE) << "doRpcCommand:" << strMethod << ":" << jvParams; + cLog(lsTRACE) << "doRpcCommand:" << strMethod << ":" << jvParams; - if (!jvParams.isArray() || jvParams.size() != 1) + if (!jvParams.isArray() || jvParams.size() > 1) return rpcError(rpcINVALID_PARAMS); - Json::Value jvRequest = jvParams[0u]; + Json::Value jvRequest = jvParams.size() ? jvParams[0u] : Json::Value(Json::objectValue); if (!jvRequest.isObject()) return rpcError(rpcINVALID_PARAMS); diff --git a/src/cpp/ripple/RPCSub.cpp b/src/cpp/ripple/RPCSub.cpp index a0759cca4..c4ecc609c 100644 --- a/src/cpp/ripple/RPCSub.cpp +++ b/src/cpp/ripple/RPCSub.cpp @@ -4,10 +4,23 @@ #include "CallRPC.h" +SETUP_LOG(); + RPCSub::RPCSub(const std::string& strUrl, const std::string& strUsername, const std::string& strPassword) : mUrl(strUrl), mUsername(strUsername), mPassword(strPassword) { - mId = 1; + std::string strScheme; + + if (!parseUrl(strUrl, strScheme, mIp, mPort, mPath)) + { + throw std::runtime_error("Failed to parse url."); + } + else if (strScheme != "http") + { + throw std::runtime_error("Only http is supported."); + } + + mSeq = 1; } void RPCSub::sendThread() @@ -33,7 +46,7 @@ void RPCSub::sendThread() mDeque.pop_front(); jvEvent = pEvent.second; - jvEvent["id"] = pEvent.first; + jvEvent["seq"] = pEvent.first; bSend = true; } @@ -43,9 +56,14 @@ void RPCSub::sendThread() if (bSend) { // Drop result. - (void) callRPC(mIp, mPort, mUsername, mPassword, "event", jvEvent); - - sendThread(); + try + { + (void) callRPC(mIp, mPort, mUsername, mPassword, mPath, "event", jvEvent); + } + catch (const std::exception& e) + { + cLog(lsDEBUG) << boost::str(boost::format("callRPC exception: %s") % e.what()); + } } } while (bSend); } @@ -61,7 +79,7 @@ void RPCSub::send(const Json::Value& jvObj) mDeque.pop_back(); } - mDeque.push_back(std::make_pair(mId++, jvObj)); + mDeque.push_back(std::make_pair(mSeq++, jvObj)); if (!mSending) { diff --git a/src/cpp/ripple/RPCSub.h b/src/cpp/ripple/RPCSub.h index 2e4921ec1..8cca3e7f5 100644 --- a/src/cpp/ripple/RPCSub.h +++ b/src/cpp/ripple/RPCSub.h @@ -17,8 +17,9 @@ class RPCSub : public InfoSub int mPort; std::string mUsername; std::string mPassword; + std::string mPath; - int mId; // Next id to allocate. + int mSeq; // Next id to allocate. bool mSending; // Sending threead is active. diff --git a/src/cpp/ripple/rpc.cpp b/src/cpp/ripple/rpc.cpp index 97731b763..5b80d3e65 100644 --- a/src/cpp/ripple/rpc.cpp +++ b/src/cpp/ripple/rpc.cpp @@ -39,11 +39,13 @@ Json::Value JSONRPCError(int code, const std::string& message) // and to be compatible with other JSON-RPC implementations. // -std::string createHTTPPost(const std::string& strMsg, const std::map& mapRequestHeaders) +std::string createHTTPPost(const std::string& strPath, const std::string& strMsg, const std::map& mapRequestHeaders) { std::ostringstream s; - s << "POST / HTTP/1.1\r\n" + s << "POST " + << (strPath.empty() ? "/" : strPath) + << " HTTP/1.1\r\n" << "User-Agent: " SYSTEM_NAME "-json-rpc/" << FormatFullVersion() << "\r\n" << "Host: 127.0.0.1\r\n" << "Content-Type: application/json\r\n" diff --git a/test/config-example.js b/test/config-example.js index 0ddf080ee..0e6a64f93 100644 --- a/test/config-example.js +++ b/test/config-example.js @@ -35,4 +35,12 @@ exports.servers = { } }; +exports.http_servers = { + // A local test server + "alpha-http" : { + "ip" : "127.0.0.1", + "port" : 8088, + } +}; + // vim:sw=2:sts=2:ts=8:et diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js new file mode 100644 index 000000000..1c1421666 --- /dev/null +++ b/test/jsonrpc-test.js @@ -0,0 +1,194 @@ +var async = require("async"); +var buster = require("buster"); +var http = require("http"); +var jsonrpc = require("simple-jsonrpc"); +var EventEmitter = require('events').EventEmitter; + +var Amount = require("../src/js/amount.js").Amount; +var Remote = require("../src/js/remote.js").Remote; +var Server = require("./server.js").Server; + +var testutils = require("./testutils.js"); + +var config = require("./config.js"); + +require("../src/js/amount.js").config = require("./config.js"); +require("../src/js/remote.js").config = require("./config.js"); + +// How long to wait for server to start. +var serverDelay = 1500; + +buster.testRunner.timeout = 5000; + +var HttpServer = function () { +}; + +var server; +var server_events; + +var build_setup = function (options) { + var setup = testutils.build_setup(options); + + return function (done) { + var self = this; + + var http_config = config.http_servers["zed"]; + + server_events = new EventEmitter; + server = http.createServer(function (req, res) { + // console.log("REQUEST"); + var input = ""; + + req.setEncoding(); + + req.on('data', function (buffer) { + // console.log("DATA: %s", buffer); + + input = input + buffer; + }); + + req.on('end', function () { + // console.log("END"); + var request = JSON.parse(input); + + // console.log("REQ: %s", JSON.stringify(request, undefined, 2)); + + server_events.emit('request', request, res); + }); + + req.on('close', function () { + // console.log("CLOSE"); + }); + }); + + server.listen(http_config.port, http_config.ip, undefined, + function () { + // console.log("server up: %s %d", http_config.ip, http_config.port); + + setup.call(self, done); + }); + }; +}; + +var build_teardown = function () { + var teardown = testutils.build_teardown(); + + return function (done) { + var self = this; + + server.close(function () { + // console.log("server closed"); + + teardown.call(self, done); + }); + }; +}; + +buster.testCase("JSON-RPC", { + setUp : build_setup(), + // setUp : build_setup({ verbose: true }), + // setUp : build_setup({verbose: true , no_server: true}), + tearDown : build_teardown(), + + "server_info" : + function (done) { + var rippled_config = config.servers.alpha; + var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port); + + client.call('server_info', [], function (result) { + // console.log(JSON.stringify(result, undefined, 2)); + buster.assert('info' in result); + + done(); + }); + }, + + "subscribe server" : + function (done) { + var rippled_config = config.servers.alpha; + var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port); + var http_config = config.http_servers["zed"]; + + client.call('subscribe', [{ + 'url' : "http://" + http_config.ip + ":" + http_config.port, + 'streams' : [ 'server' ], + }], function (result) { + // console.log(JSON.stringify(result, undefined, 2)); + + buster.assert('random' in result); + + done(); + }); + }, + + "subscribe ledger" : + function (done) { + var self = this; + + var rippled_config = config.servers.alpha; + var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port); + var http_config = config.http_servers["zed"]; + + async.waterfall([ + function (callback) { + self.what = "Subscribe."; + + client.call('subscribe', [{ + 'url' : "http://" + http_config.ip + ":" + http_config.port, + 'streams' : [ 'ledger' ], + }], function (result) { + //console.log(JSON.stringify(result, undefined, 2)); + + buster.assert('ledger_index' in result); + + callback(); + }); + }, + function (callback) { + self.what = "Accept a ledger."; + + server_events.once('request', function (request, response) { + // console.log("GOT: %s", JSON.stringify(request, undefined, 2)); + + buster.assert.equals(1, request.params.seq); + buster.assert.equals(3, request.params.ledger_index); + + response.statusCode = 200; + response.end(JSON.stringify({ + jsonrpc: "2.0", + result: {}, + id: request.id + })); + + callback(); + }); + + self.remote.ledger_accept(); + }, + function (callback) { + self.what = "Accept another ledger."; + + server_events.once('request', function (request, response) { + // console.log("GOT: %s", JSON.stringify(request, undefined, 2)); + + buster.assert.equals(2, request.params.seq); + buster.assert.equals(4, request.params.ledger_index); + + response.statusCode = 200; + response.end(JSON.stringify({ + jsonrpc: "2.0", + result: {}, + id: request.id + })); + + callback(); + }); + + self.remote.ledger_accept(); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + } +}); diff --git a/test/testutils.js b/test/testutils.js index 07fd28d37..55e87a38d 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -107,8 +107,6 @@ var build_setup = function (opts, host) { var build_teardown = function (host) { return function (done) { - - host = host || config.server_default; var data = this.store[host];