http server example

allows to use both positional and named parameters
This commit is contained in:
Ivan Tivonenko
2015-12-03 05:54:29 +02:00
parent 8d8a850864
commit 76866ab901
10 changed files with 456 additions and 2 deletions

38
npm-shrinkwrap.json generated
View File

@@ -91,6 +91,44 @@
}
}
},
"jayson": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-1.2.2.tgz",
"dependencies": {
"JSONStream": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.3.tgz",
"dependencies": {
"jsonparse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz"
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
}
}
},
"commander": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz",
"dependencies": {
"keypress": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz"
}
}
},
"eyes": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz"
},
"lodash": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz"
}
}
},
"lodash": {
"version": "3.10.1",
"from": "lodash@>=3.1.0 <4.0.0",

View File

@@ -20,6 +20,7 @@
"babel-runtime": "^5.5.4",
"bignumber.js": "^2.0.3",
"https-proxy-agent": "^1.0.0",
"jayson": "^1.2.2",
"lodash": "^3.1.0",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "^0.1.0",
@@ -65,7 +66,8 @@
"test": "istanbul test _mocha",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; echo 'parser: babel-eslint' >> eslintrc; fi; eslint -c eslintrc src/",
"perf": "./scripts/perf_test.sh"
"perf": "./scripts/perf_test.sh",
"start": "babel-node scripts/http.js"
},
"repository": {
"type": "git",

View File

@@ -34,6 +34,7 @@ unittest() {
integrationtest() {
mocha test/integration/integration-test.js
mocha test/integration/http-integration-test.js
}
doctest() {

16
scripts/http.js Normal file
View File

@@ -0,0 +1,16 @@
'use strict';
const createHTTPServer = require('../src/index').createHTTPServer;
const port = 5990;
const serverUrl = 'wss://s1.ripple.com';
function main() {
const server = createHTTPServer({server: serverUrl}, port);
server.start().then(() => {
console.log('Server started on port ' + String(port));
});
}
main();

85
src/http.js Normal file
View File

@@ -0,0 +1,85 @@
/* eslint-disable new-cap */
'use strict';
const assert = require('assert-diff');
const _ = require('lodash');
const jayson = require('jayson');
const RippleAPI = require('./api').RippleAPI;
function createHTTPServer(options, httpPort) {
const rippleAPI = new RippleAPI(options);
const methodNames = _.filter(_.keys(RippleAPI.prototype), k => {
return typeof RippleAPI.prototype[k] === 'function'
&& k !== 'connect'
&& k !== 'disconnect'
&& k !== 'constructor'
&& k !== 'RippleAPI';
});
function applyPromiseWithCallback(fnName, callback, args_) {
try {
let args = args_;
if (!_.isArray(args_)) {
const fnParameters = jayson.Utils.getParameterNames(rippleAPI[fnName]);
args = fnParameters.map(name => args_[name]);
const defaultArgs = _.omit(args_, fnParameters);
assert(_.size(defaultArgs) <= 1,
'Function must have no more than one default argument');
if (_.size(defaultArgs) > 0) {
args.push(defaultArgs[_.keys(defaultArgs)[0]]);
}
}
Promise.resolve(rippleAPI[fnName].apply(rippleAPI, args))
.then(res => callback(null, res))
.catch(err => {
callback({code: 99, message: err.message, data: {name: err.name}});
});
} catch (err) {
callback({code: 99, message: err.message, data: {name: err.name}});
}
}
const methods = {};
_.forEach(methodNames, fn => {
methods[fn] = jayson.Method((args, cb) => {
applyPromiseWithCallback(fn, cb, args);
}, {collect: true});
});
const server = jayson.server(methods);
let httpServer = null;
return {
server: server,
start: function() {
if (httpServer !== null) {
return Promise.reject('Already started');
}
return new Promise((resolve) => {
rippleAPI.connect().then(() => {
httpServer = server.http();
httpServer.listen(httpPort, resolve);
});
});
},
stop: function() {
if (httpServer === null) {
return Promise.reject('Not started');
}
return new Promise((resolve) => {
rippleAPI.disconnect();
httpServer.close(() => {
httpServer = null;
resolve();
});
});
}
};
}
module.exports = {
createHTTPServer
};

View File

@@ -2,5 +2,8 @@
module.exports = {
RippleAPI: require('./api').RippleAPI,
RippleAPIBroadcast: require('./broadcast').RippleAPIBroadcast
// Broadcast api is experimental
RippleAPIBroadcast: require('./broadcast').RippleAPIBroadcast,
// HTTP server is experimental
createHTTPServer: require('./http').createHTTPServer
};

View File

@@ -0,0 +1,74 @@
{
"jsonrpc": "2.0",
"id": "2",
"result": {
"type": "order",
"address": "rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV",
"sequence": 7973823,
"id": "4EB6B76237DEEE99F1EA16FAACED2D1E69C5F9CB54F727A4ECA51A08AD3AF466",
"specification": {
"direction": "buy",
"quantity": {
"currency": "USD",
"value": "0.000709756467",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
"totalPrice": {
"currency": "JPY",
"value": "0.086630181788",
"counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN"
}
},
"outcome": {
"result": "tesSUCCESS",
"timestamp": "2015-12-03T00:53:21.000Z",
"fee": "0.010001",
"balanceChanges": {
"rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV": [
{
"currency": "XRP",
"value": "-0.010001"
}
]
},
"orderbookChanges": {
"rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV": [
{
"direction": "buy",
"quantity": {
"currency": "USD",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0.000709260645"
},
"totalPrice": {
"currency": "JPY",
"counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN",
"value": "0.086665436143"
},
"sequence": 7973725,
"status": "cancelled",
"makerExchangeRate": "0.008183892870852266"
},
{
"direction": "buy",
"quantity": {
"currency": "USD",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0.000709756467"
},
"totalPrice": {
"currency": "JPY",
"counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN",
"value": "0.086630181788"
},
"sequence": 7973823,
"status": "created",
"makerExchangeRate": "0.008192946757712049"
}
]
},
"ledgerVersion": 17445469,
"indexInLedger": 2
}
}
}

View File

@@ -0,0 +1,72 @@
{
"jsonrpc": "2.0",
"id": "3",
"result": [
{
"type": "order",
"address": "rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM",
"sequence": 3372206,
"id": "848397FA686BD4A59F91EC1F4DE717360470EE8BD67CAA01D5FD333EDA8D97B3",
"specification": {
"direction": "buy",
"quantity": {
"currency": "JPY",
"value": "27865.90216965619",
"counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg"
},
"totalPrice": {
"currency": "XRP",
"value": "40000"
}
},
"outcome": {
"result": "tesSUCCESS",
"fee": "0.011",
"balanceChanges": {
"rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM": [
{
"currency": "XRP",
"value": "-0.011"
}
]
},
"orderbookChanges": {
"rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM": [
{
"direction": "buy",
"quantity": {
"currency": "JPY",
"counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg",
"value": "27880.6855384734"
},
"totalPrice": {
"currency": "XRP",
"value": "40000"
},
"sequence": 3372204,
"status": "cancelled",
"makerExchangeRate": "0.697017138461835"
},
{
"direction": "buy",
"quantity": {
"currency": "JPY",
"counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg",
"value": "27865.90216965619"
},
"totalPrice": {
"currency": "XRP",
"value": "40000"
},
"sequence": 3372206,
"status": "created",
"makerExchangeRate": "0.6966475542414048"
}
]
},
"ledgerVersion": 17533547,
"indexInLedger": 12
}
}
]
}

View File

@@ -0,0 +1,6 @@
'use strict';
module.exports = {
getTransaction: require('./get-transaction'),
getTransactions: require('./get-transactions')
};

View File

@@ -0,0 +1,157 @@
/* eslint-disable max-nested-callbacks */
'use strict';
const assert = require('assert-diff');
const _ = require('lodash');
const jayson = require('jayson');
const createHTTPServer = require('../../src/index').createHTTPServer;
const apiFixtures = require('../fixtures');
const apiRequests = apiFixtures.requests;
const apiResponses = apiFixtures.responses;
const fixtures = require('./fixtures');
const TIMEOUT = 20000; // how long before each test case times out
const apiOptions = {
server: 'wss://s1.ripple.com'
};
const httpPort = 3000;
function createClient() {
return jayson.client.http({port: httpPort, hostname: 'localhost'});
}
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
function makePositionalParams(params) {
return params.map(value => value[_.keys(value)[0]]);
}
function makeNamedParams(params) {
return _.reduce(params, _.assign, {});
}
function random() {
return _.fill(Array(16), 0);
}
describe('http server integration tests', function() {
this.timeout(TIMEOUT);
let server = null;
let client = null;
function createTestInternal(testName, methodName, params, testFunc, id) {
it(testName, function() {
return new Promise((resolve, reject) => {
client.request(methodName, params, id,
(err, result) => err ? reject(err) : resolve(testFunc(result)));
});
});
}
function createTest(name, params, testFunc, id) {
createTestInternal(name + ' - positional params', name,
makePositionalParams(params), testFunc, id);
createTestInternal(name + ' - named params', name,
makeNamedParams(params), testFunc, id);
}
beforeEach(function() {
server = createHTTPServer(apiOptions, httpPort);
return server.start().then(() => {
this.client = createClient();
client = this.client;
});
});
afterEach(function() {
return server.stop();
});
createTest(
'getLedgerVersion',
[],
result => assert(_.isNumber(result.result))
);
createTest(
'getServerInfo',
[],
result => assert(_.isNumber(result.result.validatedLedger.ledgerVersion))
);
createTest(
'getTransaction',
[{id: '4EB6B76237DEEE99F1EA16FAACED2D1E69C5F9CB54F727A4ECA51A08AD3AF466'}],
result => assert.deepEqual(result, fixtures.getTransaction),
'2'
);
createTest(
'getTransactions',
[{address: 'rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM'}, {
options: {
binary: true,
limit: 1,
start:
'FBAAC31D6BAEEFA9E501266FD62DA7A7982662BC19BC42F49BB41405C2F820DB'
}
}],
result => assert.deepEqual(result, fixtures.getTransactions),
'3'
);
createTest(
'prepareSettings',
[
{address},
{settings: apiRequests.prepareSettings},
{instructions: {
maxFee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051
}}
],
result => {
const got = JSON.parse(result.result.txJSON);
const expected = JSON.parse(apiResponses.prepareSettings.flags.txJSON);
assert.deepEqual(got, expected);
}
);
createTest(
'sign',
[{txJSON: apiRequests.sign.normal.txJSON},
{secret: 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'}],
result => assert.deepEqual(result.result, apiResponses.sign.normal)
);
createTest(
'generateAddress',
[{options: {entropy: random()}}],
result => assert.deepEqual(result.result, apiResponses.generateAddress)
);
createTest(
'computeLedgerHash',
[{ledger: _.assign({}, apiResponses.getLedger.full,
{parentCloseTime: apiResponses.getLedger.full.closeTime})
}],
result => {
assert.strictEqual(result.result,
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E');
}
);
createTest(
'isConnected',
[],
result => assert(result.result)
);
});