Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StellarTomlResolver timeout option #166

Merged
merged 8 commits into from
Jun 5, 2018
Merged
23 changes: 22 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import clone from 'lodash/clone';

let defaultConfig = {
allowHttp: false
allowHttp: false,
timeout: 0
};

let config = clone(defaultConfig);
Expand All @@ -13,11 +14,13 @@ let config = clone(defaultConfig);
* ```
* import {Config} from 'stellar-sdk';
* Config.setAllowHttp(true);
* Config.setTimout(5000);
* ```
*
* Usage browser:
* ```
* StellarSdk.Config.setAllowHttp(true);
* StellarSdk.Config.setTimout(5000);
* ```
* @static
*/
Expand All @@ -32,6 +35,16 @@ class Config {
config.allowHttp = value;
}

/**
* Sets `timeout` flag globally. When set to anything besides 0, the request will timeout after specified time (ms).
* Default: 0.
* @param {number} value
* @static
*/
static setTimeout(value) {
config.timeout = value;
}

/**
* Returns the value of `allowHttp` flag.
* @static
Expand All @@ -40,6 +53,14 @@ class Config {
return clone(config.allowHttp);
}

/**
* Returns the value of `timeout` flag.
* @static
*/
static getTimeout() {
return clone(config.timeout);
}

/**
* Sets all global config flags to default values.
* @static
Expand Down
14 changes: 12 additions & 2 deletions src/federation_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const FEDERATION_RESPONSE_MAX_SIZE = 100 * 1024;
* @param {string} domain Domain this server represents
* @param {object} [opts]
* @param {boolean} [opts.allowHttp] - Allow connecting to http servers, default: `false`. This must be set to false in production deployments! You can also use {@link Config} class to set this globally.
* @param {number} [opts.timeout] - Allow a timeout, default: 0. Allows user to avoid nasty lag due to TOML resolve issue. You can also use {@link Config} class to set this globally.
*/
export class FederationServer {
constructor(serverURL, domain, opts = {}) {
Expand All @@ -32,6 +33,11 @@ export class FederationServer {
allowHttp = opts.allowHttp;
}

this.timeout = Config.getTimeout();
if (typeof opts.timeout === 'number') {
this.timeout = opts.timeout;
}

if (this.serverURL.protocol() != 'https' && !allowHttp) {
throw new Error('Cannot connect to insecure federation server');
}
Expand Down Expand Up @@ -71,6 +77,7 @@ export class FederationServer {
* @param {string} value Stellar Address (ex. `bob*stellar.org`)
* @param {object} [opts]
* @param {boolean} [opts.allowHttp] - Allow connecting to http servers, default: `false`. This must be set to false in production deployments!
* @param {number} [opts.timeout] - Allow a timeout, default: 0. Allows user to avoid nasty lag due to TOML resolve issue.
* @returns {Promise}
*/
static resolve(value, opts = {}) {
Expand Down Expand Up @@ -109,10 +116,11 @@ export class FederationServer {
* @param {string} domain Domain to get federation server for
* @param {object} [opts]
* @param {boolean} [opts.allowHttp] - Allow connecting to http servers, default: `false`. This must be set to false in production deployments!
* @param {number} [opts.timeout] - Allow a timeout, default: 0. Allows user to avoid nasty lag due to TOML resolve issue.
* @returns {Promise}
*/
static createForDomain(domain, opts = {}) {
return StellarTomlResolver.resolve(domain)
return StellarTomlResolver.resolve(domain, opts)
.then(tomlObject => {
if (!tomlObject.FEDERATION_SERVER) {
return Promise.reject(new Error('stellar.toml does not contain FEDERATION_SERVER field'));
Expand Down Expand Up @@ -161,7 +169,9 @@ export class FederationServer {
}

_sendRequest(url) {
return axios.get(url.toString(), {maxContentLength: FEDERATION_RESPONSE_MAX_SIZE})
let timeout = this.timeout;

return axios.get(url.toString(), {maxContentLength: FEDERATION_RESPONSE_MAX_SIZE, timeout})
.then(response => {
if (typeof response.data.memo != "undefined" && typeof response.data.memo != 'string') {
throw new Error("memo value should be of type string");
Expand Down
10 changes: 9 additions & 1 deletion src/stellar_toml_resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,27 @@ export class StellarTomlResolver {
* @param {string} domain Domain to get stellar.toml file for
* @param {object} [opts]
* @param {boolean} [opts.allowHttp] - Allow connecting to http servers, default: `false`. This must be set to false in production deployments!
* @param {number} [opts.timeout] - Allow a timeout, default: 0. Allows user to avoid nasty lag due to TOML resolve issue.
* @returns {Promise}
*/
static resolve(domain, opts = {}) {
let allowHttp = Config.isAllowHttp();
let timeout = Config.getTimeout();

if (typeof opts.allowHttp !== 'undefined') {
allowHttp = opts.allowHttp;
}

if (typeof opts.timeout === 'number') {
timeout = opts.timeout;
}

let protocol = 'https';
if (allowHttp) {
protocol = 'http';
}
return axios.get(`${protocol}://${domain}/.well-known/stellar.toml`, {maxContentLength: STELLAR_TOML_MAX_SIZE})

return axios.get(`${protocol}://${domain}/.well-known/stellar.toml`, {maxContentLength: STELLAR_TOML_MAX_SIZE, timeout})
.then(response => {
try {
let tomlObject = toml.parse(response.data);
Expand Down
102 changes: 102 additions & 0 deletions test/unit/federation_server_test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import http from "http";
import { Config } from "../../src/config";

describe("federation-server.js tests", function () {
beforeEach(function () {
Expand Down Expand Up @@ -227,5 +228,106 @@ FEDERATION_SERVER="https://api.stellar.org/federation"
.then(() => tempServer.close());
});
});

});

describe('FederationServer times out when response lags and timeout set', function () {
afterEach(function() {
Config.setDefault();
});

let opts = {allowHttp: true};
let message;
for (let i = 0; i < 2; i++) {
if (i === 0) {
Config.setTimeout(1000);
message = "with global config set";
} else {
opts = {allowHttp: true, timeout: 1000};
message = "with instance opts set";
}

it(`resolveAddress times out ${message}`, function (done) {
// Unable to create temp server in a browser
if (typeof window != 'undefined') {
return done();
}

let tempServer = http.createServer((req, res) => {
setTimeout(() => {}, 10000);
}).listen(4444, () => {
new StellarSdk.FederationServer('http://localhost:4444/federation', 'stellar.org', opts)
.resolveAddress('bob*stellar.org')
.should.be.rejectedWith(/timeout of 1000ms exceeded/)
.notify(done)
.then(() => tempServer.close());
});
});

it(`resolveAccountId times out ${message}`, function (done) {
// Unable to create temp server in a browser
if (typeof window != 'undefined') {
return done();
}
let tempServer = http.createServer((req, res) => {
setTimeout(() => {}, 10000);
}).listen(4444, () => {
new StellarSdk.FederationServer('http://localhost:4444/federation', 'stellar.org', opts)
.resolveAccountId('GB5XVAABEQMY63WTHDQ5RXADGYF345VWMNPTN2GFUDZT57D57ZQTJ7PS')
.should.be.rejectedWith(/timeout of 1000ms exceeded/)
.notify(done)
.then(() => tempServer.close());
});
});

it(`resolveTransactionId times out ${message}`, function (done) {
// Unable to create temp server in a browser
if (typeof window != 'undefined') {
return done();
}
let tempServer = http.createServer((req, res) => {
setTimeout(() => {}, 10000);
}).listen(4444, () => {
new StellarSdk.FederationServer('http://localhost:4444/federation', 'stellar.org', opts)
.resolveTransactionId('3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889')
.should.be.rejectedWith(/timeout of 1000ms exceeded/)
.notify(done)
.then(() => tempServer.close());
});
});

it(`createForDomain times out ${message}`, function (done) {
// Unable to create temp server in a browser
if (typeof window != 'undefined') {
return done();
}
let tempServer = http.createServer((req, res) => {
setTimeout(() => {}, 10000);
}).listen(4444, () => {
StellarSdk.FederationServer
.createForDomain("localhost:4444", opts)
.should.be.rejectedWith(/timeout of 1000ms exceeded/)
.notify(done)
.then(() => tempServer.close());
});
});

it(`resolve times out ${message}`, function (done) {
// Unable to create temp server in a browser
if (typeof window != 'undefined') {
return done();
}

let tempServer = http.createServer((req, res) => {
setTimeout(() => {}, 10000);
}).listen(4444, () => {
StellarSdk.FederationServer
.resolve('bob*localhost:4444', opts)
.should.eventually.be.rejectedWith(/timeout of 1000ms exceeded/)
.notify(done)
.then(() => tempServer.close());
});
});
}
});
});
41 changes: 41 additions & 0 deletions test/unit/stellar_toml_resolver_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ describe("stellar_toml_resolver.js tests", function () {
});

describe('StellarTomlResolver.resolve', function () {
afterEach(function() {
StellarSdk.Config.setDefault();
});

it("returns stellar.toml object for valid request and stellar.toml file", function (done) {
this.axiosMock.expects('get')
.withArgs(sinon.match('https://acme.com/.well-known/stellar.toml'))
Expand Down Expand Up @@ -106,5 +110,42 @@ FEDERATION_SERVER="https://api.stellar.org/federation"
.then(() => tempServer.close());
});
});

it("rejects after given timeout when global Config.timeout flag is set", function (done) {
StellarSdk.Config.setTimeout(1000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add Config.setDefault(); to afterEach here too.


// Unable to create temp server in a browser
if (typeof window != 'undefined') {
return done();
}

let tempServer = http.createServer((req, res) => {
setTimeout(() => {}, 10000);
}).listen(4444, () => {
StellarSdk.StellarTomlResolver.resolve("localhost:4444", {allowHttp: true})
.should.be.rejectedWith(/timeout of 1000ms exceeded/)
.notify(done)
.then(() => {
StellarSdk.Config.setDefault();
tempServer.close();
});
});
});

it("rejects after given timeout when timeout specified in StellarTomlResolver opts param", function (done) {
// Unable to create temp server in a browser
if (typeof window != 'undefined') {
return done();
}

let tempServer = http.createServer((req, res) => {
setTimeout(() => {}, 10000);
}).listen(4444, () => {
StellarSdk.StellarTomlResolver.resolve("localhost:4444", {allowHttp: true, timeout: 1000})
.should.be.rejectedWith(/timeout of 1000ms exceeded/)
.notify(done)
.then(() => tempServer.close());
});
});
});
});