Skip to content
Snippets Groups Projects
Commit de2244a8 authored by Roger Jungemann's avatar Roger Jungemann
Browse files

Opt-in retriability for HTTP transport

parent 11b44587
No related branches found
No related tags found
No related merge requests found
......@@ -47,6 +47,7 @@
"detect-node": "^2.0.3",
"ecurve": "^1.0.5",
"lodash": "^4.16.4",
"retry": "^0.12.0",
"secure-random": "^1.1.1",
"ws": "^3.3.2"
},
......
......@@ -27,7 +27,6 @@ export default class Transport extends EventEmitter {
send() {}
start() {}
stop() {}
}
Promise.promisifyAll(Transport.prototype);
import fetch from 'cross-fetch';
import newDebug from 'debug';
import retry from 'retry';
import Transport from './base';
const debug = newDebug('steem:http');
......@@ -13,9 +14,9 @@ class RPCError extends Error {
}
}
export function jsonRpc(uri, {method, id, params}) {
export function jsonRpc(uri, {method, id, params, fetchMethod}) {
const payload = {id, jsonrpc: '2.0', method, params};
return fetch(uri, {
return (fetchMethod || fetch)(uri, {
body: JSON.stringify(payload),
method: 'post',
mode: 'cors',
......@@ -42,12 +43,47 @@ export function jsonRpc(uri, {method, id, params}) {
export default class HttpTransport extends Transport {
send(api, data, callback) {
if (this.options.useAppbaseApi) {
api = 'condenser_api';
api = 'condenser_api';
}
debug('Steem::send', api, data);
const id = data.id || this.id++;
const params = [api, data.method, data.params];
jsonRpc(this.options.uri, {method: 'call', id, params})
.then(res => { callback(null, res) }, err => { callback(err) })
const retriable = this.retriable(api, data);
const fetchMethod = this.options.fetchMethod;
retriable.attempt((currentAttempt) => {
jsonRpc(this.options.uri, { method: 'call', id, params, fetchMethod }).then(
res => { callback(null, res); },
err => {
if (retriable.retry(err)) return;
callback(retriable.mainError());
}
);
});
}
get nonRetriableOperations() {
return this.options.nonRetriableOperations || [
'broadcast_transaction',
'broadcast_transaction_with_callback',
'broadcast_transaction_synchronous',
'broadcast_block',
];
}
// An object which can be used to track retries.
retriable(api, data) {
if (this.nonRetriableOperations.some((o) => o === data.method)) {
// Do not retry if the operation is non-retriable.
return retry.operation({ retries: 0 });
} else if (this.options.retry) {
// If `this.options.retry` is a map of options, use it. If
// `this.options.retry` is `true`, use default options.
return (Object(this.options.retry) === this.options.retry) ?
retry.operation(this.options.retry) :
retry.operation();
} else {
// Otherwise, don't retry.
return retry.operation({ retries: 0 });
}
}
}
......@@ -150,4 +150,180 @@ describe('steem.api:', function () {
});
});
describe('with retry', () => {
afterEach(() => {
// NOTE: We should reset `steem.api.options` after *every* test.
steem.api.setOptions({
url: 'https://api.steemit.com',
retry: null,
fetchMethod: null,
});
});
it('works by default', async function() {
let attempts = 0;
steem.api.setOptions({
url: 'https://api.steemit.com',
fetchMethod: (uri, req) => new Promise((res, rej) => {
const data = JSON.parse(req.body);
res({
ok: true,
json: () => Promise.resolve({
jsonrpc: '2.0',
id: data.id,
result: ['ned'],
}),
});
attempts++;
}),
});
const result = await steem.api.getFollowersAsync('ned', 0, 'blog', 5)
assert.equal(attempts, 1);
assert.deepEqual(result, ['ned']);
});
it('does not retry by default', async() => {
let attempts = 0;
steem.api.setOptions({
url: 'https://api.steemit.com',
fetchMethod: (uri, req) => new Promise((res, rej) => {
rej(new Error('Bad request'));
attempts++;
}),
});
let result;
let errored = false;
try {
result = await steem.api.getFollowersAsync('ned', 0, 'blog', 5)
} catch (e) {
errored = true;
}
assert.equal(attempts, 1);
assert.equal(errored, true);
});
it('works with retry passed as a boolean', async() => {
let attempts = 0;
steem.api.setOptions({
url: 'https://api.steemit.com',
fetchMethod: (uri, req) => new Promise((res, rej) => {
const data = JSON.parse(req.body);
res({
ok: true,
json: () => Promise.resolve({
jsonrpc: '2.0',
id: data.id,
result: ['ned'],
}),
});
attempts++;
}),
});
const result = await steem.api.getFollowersAsync('ned', 0, 'blog', 5)
assert.equal(attempts, 1);
assert.deepEqual(result, ['ned']);
});
it('retries with retry passed as a boolean', async() => {
let attempts = 0;
steem.api.setOptions({
url: 'https://api.steemit.com',
retry: true,
fetchMethod: (uri, req) => new Promise((res, rej) => {
if (attempts < 1) {
rej(new Error('Bad request'));
} else {
const data = JSON.parse(req.body);
res({
ok: true,
json: () => Promise.resolve({
jsonrpc: '2.0',
id: data.id,
result: ['ned'],
}),
});
}
attempts++;
}),
});
let result;
let errored = false;
try {
result = await steem.api.getFollowersAsync('ned', 0, 'blog', 5);
} catch (e) {
errored = true;
}
assert.equal(attempts, 2);
assert.equal(errored, false);
assert.deepEqual(result, ['ned']);
});
it('works with retry passed as an object', async() => {
steem.api.setOptions({
url: 'https://api.steemit.com',
retry: {
retries: 3,
minTimeout: 1, // 1ms
},
fetchMethod: (uri, req) => new Promise((res, rej) => {
const data = JSON.parse(req.body);
res({
ok: 'true',
json: () => Promise.resolve({
jsonrpc: '2.0',
id: data.id,
result: ['ned'],
}),
});
}),
});
const result = await steem.api.getFollowersAsync('ned', 0, 'blog', 5);
assert.deepEqual(result, ['ned']);
});
it('retries with retry passed as an object', async() => {
let attempts = 0;
steem.api.setOptions({
url: 'https://api.steemit.com',
retry: {
retries: 3,
minTimeout: 1,
},
fetchMethod: (uri, req) => new Promise((res, rej) => {
if (attempts < 1) {
rej(new Error('Bad request'));
} else {
const data = JSON.parse(req.body);
res({
ok: true,
json: () => Promise.resolve({
jsonrpc: '2.0',
id: data.id,
result: ['ned'],
}),
});
}
attempts++;
}),
});
let result;
let errored = false;
try {
result = await steem.api.getFollowersAsync('ned', 0, 'blog', 5);
} catch (e) {
errored = true;
}
assert.equal(attempts, 2);
assert.equal(errored, false);
assert.deepEqual(result, ['ned']);
});
it('does not retry non-retriable operations');
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment