From 5934f77b60629aad465cfbb4bd3473a67954b2ea Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:00:49 -0800 Subject: [PATCH 01/10] Add simple-jsonrpc to npm dependancies. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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", From 6ba31accb2fa05d49cea82ff394ff8ced62a665f Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:01:34 -0800 Subject: [PATCH 02/10] Fixes for rpc subscriptions. --- src/cpp/ripple/RPCSub.cpp | 34 ++++++++++++++++++++++++++++++---- src/cpp/ripple/RPCSub.h | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/cpp/ripple/RPCSub.cpp b/src/cpp/ripple/RPCSub.cpp index a0759cca4..35ec7b719 100644 --- a/src/cpp/ripple/RPCSub.cpp +++ b/src/cpp/ripple/RPCSub.cpp @@ -4,10 +4,29 @@ #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; + std::string strPath; + + if (!parseUrl(strUrl, strScheme, mIp, mPort, strPath)) + { + throw std::runtime_error("Failed to parse url."); + } + else if (strScheme != "http") + { + throw std::runtime_error("Only http is supported."); + } + else if (!strPath.empty()) + { + // XXX FIXME: support path + throw std::runtime_error("Only empty path is supported."); + } + + mSeq = 1; } void RPCSub::sendThread() @@ -33,7 +52,7 @@ void RPCSub::sendThread() mDeque.pop_front(); jvEvent = pEvent.second; - jvEvent["id"] = pEvent.first; + jvEvent["seq"] = pEvent.first; bSend = true; } @@ -43,7 +62,14 @@ void RPCSub::sendThread() if (bSend) { // Drop result. - (void) callRPC(mIp, mPort, mUsername, mPassword, "event", jvEvent); + try + { + (void) callRPC(mIp, mPort, mUsername, mPassword, "event", jvEvent); + } + catch (const std::exception& e) + { + cLog(lsDEBUG) << boost::str(boost::format("callRPC exception: %s") % e.what()); + } sendThread(); } @@ -61,7 +87,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..1ab72c917 100644 --- a/src/cpp/ripple/RPCSub.h +++ b/src/cpp/ripple/RPCSub.h @@ -18,7 +18,7 @@ class RPCSub : public InfoSub std::string mUsername; std::string mPassword; - int mId; // Next id to allocate. + int mSeq; // Next id to allocate. bool mSending; // Sending threead is active. From 6592f9de399c90fefee878f5a82122c8480c1474 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:02:01 -0800 Subject: [PATCH 03/10] Report correct target for callRPC. --- src/cpp/ripple/CallRPC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index 6cf4ffa9a..c04f1cdf6 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -601,7 +601,7 @@ Json::Value callRPC(const std::string& strIp, const int iPort, const std::string { // 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); From 7fd00894b96a73b27ec8ee8d296496d0aed5a431 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:02:35 -0800 Subject: [PATCH 04/10] Allow empty array to for empty object for JSON-RPC. --- src/cpp/ripple/RPCHandler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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); From 1dcc62f2adcc0dc6733ca6959dff3442c15792af Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:04:17 -0800 Subject: [PATCH 05/10] Add a http server config to the example test config. --- test/config-example.js | 8 ++++++++ test/testutils.js | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) 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/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]; From 19cc1c88f750aec88c542f8e98da8cacbb599faa Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:04:54 -0800 Subject: [PATCH 06/10] UT: jsonrpc tests - including subscribe. --- test/jsonrpc-test.js | 247 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 test/jsonrpc-test.js diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js new file mode 100644 index 000000000..88fe47521 --- /dev/null +++ b/test/jsonrpc-test.js @@ -0,0 +1,247 @@ +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(); + }); + }, + +// var self = this; +// var ledgers = 20; +// var got_proposed; +// +// this.remote.transaction() +// .payment('root', 'alice', "1") +// .on('success', function (r) { +// // Transaction sent. +// +// // console.log("success: %s", JSON.stringify(r)); +// }) +// .on('pending', function() { +// // Moving ledgers along. +// // console.log("missing: %d", ledgers); +// +// ledgers -= 1; +// if (ledgers) { +// self.remote.ledger_accept(); +// } +// else { +// buster.assert(false, "Final never received."); +// done(); +// } +// }) +// .on('lost', function () { +// // Transaction did not make it in. +// // console.log("lost"); +// +// buster.assert(true); +// done(); +// }) +// .on('proposed', function (m) { +// // Transaction got an error. +// // console.log("proposed: %s", JSON.stringify(m)); +// +// buster.assert.equals(m.result, 'tecNO_DST_INSUF_XRP'); +// +// got_proposed = true; +// +// self.remote.ledger_accept(); // Move it along. +// }) +// .on('final', function (m) { +// // console.log("final: %s", JSON.stringify(m, undefined, 2)); +// +// buster.assert.equals(m.metadata.TransactionResult, 'tecNO_DST_INSUF_XRP'); +// done(); +// }) +// .on('error', function(m) { +// // console.log("error: %s", m); +// +// buster.assert(false); +// }) +// .submit(); +}); From bea23a6372380883b00ff382dc79f4e005558bba Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:16:16 -0800 Subject: [PATCH 07/10] UT: Clean up. --- test/jsonrpc-test.js | 58 ++------------------------------------------ 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js index 88fe47521..5965807bf 100644 --- a/test/jsonrpc-test.js +++ b/test/jsonrpc-test.js @@ -120,7 +120,7 @@ buster.testCase("JSON-RPC", { }); }, - "=>subscribe ledger" : + "subscribe ledger" : function (done) { var self = this; @@ -189,59 +189,5 @@ buster.testCase("JSON-RPC", { buster.refute(error, self.what); done(); }); - }, - -// var self = this; -// var ledgers = 20; -// var got_proposed; -// -// this.remote.transaction() -// .payment('root', 'alice', "1") -// .on('success', function (r) { -// // Transaction sent. -// -// // console.log("success: %s", JSON.stringify(r)); -// }) -// .on('pending', function() { -// // Moving ledgers along. -// // console.log("missing: %d", ledgers); -// -// ledgers -= 1; -// if (ledgers) { -// self.remote.ledger_accept(); -// } -// else { -// buster.assert(false, "Final never received."); -// done(); -// } -// }) -// .on('lost', function () { -// // Transaction did not make it in. -// // console.log("lost"); -// -// buster.assert(true); -// done(); -// }) -// .on('proposed', function (m) { -// // Transaction got an error. -// // console.log("proposed: %s", JSON.stringify(m)); -// -// buster.assert.equals(m.result, 'tecNO_DST_INSUF_XRP'); -// -// got_proposed = true; -// -// self.remote.ledger_accept(); // Move it along. -// }) -// .on('final', function (m) { -// // console.log("final: %s", JSON.stringify(m, undefined, 2)); -// -// buster.assert.equals(m.metadata.TransactionResult, 'tecNO_DST_INSUF_XRP'); -// done(); -// }) -// .on('error', function(m) { -// // console.log("error: %s", m); -// -// buster.assert(false); -// }) -// .submit(); + } }); From 937f9718f8339b151a0f38a0a757fbecef5812ed Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:16:53 -0800 Subject: [PATCH 08/10] Support paths for subscribe via json-rpc. --- src/cpp/ripple/CallRPC.cpp | 5 +++-- src/cpp/ripple/CallRPC.h | 2 +- src/cpp/ripple/RPC.h | 2 +- src/cpp/ripple/RPCSub.cpp | 10 ++-------- src/cpp/ripple/RPCSub.h | 1 + src/cpp/ripple/rpc.cpp | 6 ++++-- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index c04f1cdf6..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,7 +598,7 @@ 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) @@ -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/RPCSub.cpp b/src/cpp/ripple/RPCSub.cpp index 35ec7b719..871803e2d 100644 --- a/src/cpp/ripple/RPCSub.cpp +++ b/src/cpp/ripple/RPCSub.cpp @@ -10,9 +10,8 @@ RPCSub::RPCSub(const std::string& strUrl, const std::string& strUsername, const : mUrl(strUrl), mUsername(strUsername), mPassword(strPassword) { std::string strScheme; - std::string strPath; - if (!parseUrl(strUrl, strScheme, mIp, mPort, strPath)) + if (!parseUrl(strUrl, strScheme, mIp, mPort, mPath)) { throw std::runtime_error("Failed to parse url."); } @@ -20,11 +19,6 @@ RPCSub::RPCSub(const std::string& strUrl, const std::string& strUsername, const { throw std::runtime_error("Only http is supported."); } - else if (!strPath.empty()) - { - // XXX FIXME: support path - throw std::runtime_error("Only empty path is supported."); - } mSeq = 1; } @@ -64,7 +58,7 @@ void RPCSub::sendThread() // Drop result. try { - (void) callRPC(mIp, mPort, mUsername, mPassword, "event", jvEvent); + (void) callRPC(mIp, mPort, mUsername, mPassword, mPath, "event", jvEvent); } catch (const std::exception& e) { diff --git a/src/cpp/ripple/RPCSub.h b/src/cpp/ripple/RPCSub.h index 1ab72c917..8cca3e7f5 100644 --- a/src/cpp/ripple/RPCSub.h +++ b/src/cpp/ripple/RPCSub.h @@ -17,6 +17,7 @@ class RPCSub : public InfoSub int mPort; std::string mUsername; std::string mPassword; + std::string mPath; int mSeq; // Next id to allocate. 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" From 2d3994d1d354304f2b4779bbefd0e527afe9c256 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 17:17:43 -0800 Subject: [PATCH 09/10] UT: clean up. --- test/jsonrpc-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js index 5965807bf..1c1421666 100644 --- a/test/jsonrpc-test.js +++ b/test/jsonrpc-test.js @@ -113,7 +113,8 @@ buster.testCase("JSON-RPC", { 'url' : "http://" + http_config.ip + ":" + http_config.port, 'streams' : [ 'server' ], }], function (result) { - console.log(JSON.stringify(result, undefined, 2)); + // console.log(JSON.stringify(result, undefined, 2)); + buster.assert('random' in result); done(); From c785b5bb923c0826f48fceacabfc3c83fc3c4c1f Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 30 Dec 2012 18:24:25 -0800 Subject: [PATCH 10/10] Fix a bug in rpc subscribe. --- src/cpp/ripple/RPCSub.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/cpp/ripple/RPCSub.cpp b/src/cpp/ripple/RPCSub.cpp index 871803e2d..c4ecc609c 100644 --- a/src/cpp/ripple/RPCSub.cpp +++ b/src/cpp/ripple/RPCSub.cpp @@ -64,8 +64,6 @@ void RPCSub::sendThread() { cLog(lsDEBUG) << boost::str(boost::format("callRPC exception: %s") % e.what()); } - - sendThread(); } } while (bSend); }