diff --git a/.travis.yml b/.travis.yml index 09e26b4..2408fd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ sudo: false language: node_js node_js: - - 6 - - 4 + - '6' + - '4' script: - npm run ci after_script: diff --git a/README.md b/README.md index 0e73d42..94eb856 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ failover-dns Use local cache dns query result when dns query fail. +- Support dns lookup with `options.timeout`. + ## Installation ```bash @@ -37,12 +39,26 @@ const dns = require('failover-dns'); // must listen `error` event to logging by yourself dns.on('error', err => console.error(err)); -dns.lookup('cnpmjs.org', { family: 4 }, (err, ip, family) => { +dns.lookup('cnpmjs.org', { family: 4, timeout: 2000 }, (err, ip, family) => { if (err) throw err; console.log(ip, family); }); ``` +## Default dns query timeout + +Default is `0`: + +```js +exports.defaultTimeout = 0; +``` + +Maybe you want to set global default timeout is `2000ms`: + +```js +exports.defaultTimeout = 2000; +``` + ## License [MIT](LICENSE) diff --git a/index.js b/index.js index 8891f41..21a3995 100644 --- a/index.js +++ b/index.js @@ -9,19 +9,28 @@ module.exports = exports = new EventEmitter(); exports.DNS_LOOKUP_CACHE = DNS_LOOKUP_CACHE; +// you can set global timeout, default is 0 +exports.defaultTimeout = 0; + exports.lookup = function lookup(hostname, options, callback) { if (typeof options === 'function') { callback = options; - options = {}; + options = null; } + options = options || {}; + options.timeout = options.timeout || exports.defaultTimeout; + + // don't failover on `options.all = true` if (options.all) { - // don't failover on options.all return dns.lookup(hostname, options, callback); } const cacheKey = `${hostname}_${options.family}_${options.hints}`; - dns.lookup(hostname, options, (err, ip, family) => { + exports._lookupWithTimeout(hostname, options, (err, ip, family) => { if (err) { + if (err.name === 'Error') { + err.name = 'DNSLookupError'; + } const address = DNS_LOOKUP_CACHE[cacheKey]; if (address) { // emit error event for logging @@ -49,3 +58,28 @@ exports.lookup = function lookup(hostname, options, callback) { callback(null, ip, family); }); }; + +exports._lookupWithTimeout = function lookupWithTimeout(hostname, options, callback) { + if (!options.timeout) { + return dns.lookup(hostname, options, callback); + } + + let timer = setTimeout(() => { + timer = null; + const cb = callback; + callback = null; + const err = new Error(`getaddrinfo TIMEOUT ${hostname}`); + err.name = 'DNSLookupTimeoutError'; + err.options = options; + err.code = 'TIMEOUT'; + cb(err); + }, options.timeout); + + dns.lookup(hostname, options, (err, ip, family) => { + timer && clearTimeout(timer); + if (!callback) { + return; + } + callback(err, ip, family); + }); +}; diff --git a/package.json b/package.json index 1476099..d264ec7 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "index.js" ], "scripts": { - "test": "npm run lint && mocha -t 5000 -r intelli-espower-loader test/*.test.js", - "test-cov": "istanbul cover _mocha -- -t 5000 -r intelli-espower-loader test/*.test.js", + "test": "npm run lint && mocha -t 15000 -r intelli-espower-loader test/*.test.js", + "test-cov": "istanbul cover _mocha -- -t 15000 -r intelli-espower-loader test/*.test.js", "lint": "eslint test *.js", "ci": "npm run lint && npm run test-cov", "autod": "autod -w --prefix '^'" diff --git a/test/failover-dns.test.js b/test/failover-dns.test.js index dca31c4..bd88887 100644 --- a/test/failover-dns.test.js +++ b/test/failover-dns.test.js @@ -18,6 +18,26 @@ describe('test/failover-dns.test.js', () => { }); }); + it('should lookup with options=null from dns server', done => { + dns.lookup('a.alipayobjects.com', null, (err, ip, family) => { + console.log(err, ip, family); + assert(err == null); + assert((/^\d+\.\d+\.\d+\.\d+$/).test(ip)); + assert(family === 4); + done(); + }); + }); + + it('should lookup with options.timeout', done => { + dns.lookup('as.alipayobjects.com', { timeout: 5000 }, (err, ip, family) => { + console.log(err, ip, family); + assert(err == null); + assert((/^\d+\.\d+\.\d+\.\d+$/).test(ip)); + assert(family === 4); + done(); + }); + }); + it('should lookup with options.family from dns server', done => { dns.lookup('cnpmjs.org', { family: 4 }, (err, ip, family) => { console.log(err, ip, family); @@ -66,6 +86,48 @@ describe('test/failover-dns.test.js', () => { }); }); + it('should mock timeout then return address from local cache', done => { + done = pedding(2, done); + mm(require('dns'), 'lookup', (hostname, options, callback) => { + setTimeout(() => { + callback(null, '127.0.0.1', 4); + }, 600); + }); + + dns.once('error', err => { + console.log(err); + assert(err.name === 'DNSLookupTimeoutError'); + assert(err.message === 'getaddrinfo TIMEOUT a.alipayobjects.com'); + done(); + }); + dns.lookup('a.alipayobjects.com', { family: 4, timeout: 500 }, (err, ip, family) => { + console.log('lookup result', err, ip, family); + assert(err == null); + assert((/^\d+\.\d+\.\d+\.\d+$/).test(ip)); + assert(family === 4); + // wait for dns.lookup() done and ignore callback + setTimeout(done, 200); + }); + }); + + it('should mock timeout and local cache missing', done => { + mm(require('dns'), 'lookup', (hostname, options, callback) => { + setTimeout(() => { + callback(null, '127.0.0.1', 4); + }, 600); + }); + + dns.lookup('foo.cnpmjs.org', { family: 4, timeout: 500 }, (err, ip, family) => { + console.log('lookup result', err, ip, family); + assert(err); + assert(err.name === 'DNSLookupTimeoutError'); + assert(err.message === 'getaddrinfo TIMEOUT foo.cnpmjs.org'); + assert(err.code === 'TIMEOUT'); + // wait for dns.lookup() done and ignore callback + setTimeout(done, 200); + }); + }); + it('should lookup with options.all from dns server', done => { dns.lookup('a.alipayobjects.com', { all: true }, (err, ips) => { console.log(err, ips); @@ -80,6 +142,8 @@ describe('test/failover-dns.test.js', () => { dns.lookup('cnpmjs.org', { family: 6 }, (err, ip, family) => { console.log(err, ip, family); assert(err); + assert(err.name === 'DNSLookupError'); + assert(err.message === 'getaddrinfo ENOTFOUND cnpmjs.org'); done(); }); });