package/package.json000644 0000001702 3560116604 011546 0ustar00000000 000000 { "name": "dnscache", "description": "dnscache for Node", "author": "Vinit Sacheti ", "version": "1.0.2", "keywords": [ "dnscache", "dns" ], "main": "./lib/index.js", "scripts": { "pretest": "jshint --config ./node_modules/yui-lint/jshint.json ./lib/ ./test/", "test": "nyc mocha ./test/*.js", "posttest": "nyc check-coverage --lines 100 --functions 100 --branches 100 && nyc report --reporter=lcov" }, "bugs": { "url": "http://github.com/yahoo/dnscache/issues" }, "license": "BSD", "repository": { "type": "git", "url": "http://github.com/yahoo/dnscache.git" }, "devDependencies": { "async": "^2.6.2", "jshint": "^2.10.2", "mocha": "^6.1.4", "nyc": "^14.0.0", "yui-lint": "^0.2.0" }, "dependencies": { "asap": "^2.0.6", "lodash.clone": "^4.5.0" } } package/-n000644 0000000000 3560116604 007503 0ustar00000000 000000 package/.travis.yml000644 0000000161 3560116604 011367 0ustar00000000 000000 language: node_js node_js: - "6" - "8" - "10" - "11" before_install: npm install -g npm@latest-2 package/LICENSE000644 0000002755 3560116604 010276 0ustar00000000 000000 Copyright (c) 2013, Yahoo! Inc. All rights reserved. Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Yahoo! Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Yahoo! Inc. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package/README.md000644 0000005020 3560116604 010534 0ustar00000000 000000 dnscache for Node =================== This module wraps the [dns](http://nodejs.org/api/dns.html) module methods and provide a caching layer in between. Every call to a dns method is first looked into the local cache, in case of cache hit the value from cache is returned, in case of cache miss the original dns call is made and the return value is cached in the local cache. It is very similar to GOF Proxy design pattern providing a Cache Proxy. The goal of this module is to cache the most used/most recent dns calls, to avoid the network delay and improve the performance. Once this module is enabled, all the subsequent calls to `require('dns')` are wrapped too. **NOTE:** There are situations where the built-in `dns` functions would throw, rather than call back with an error. Due to the fact that asynchronous caching mechanisms are supported, all errors for these functions will be passed as the first argument to the callback. Installation ------------ `npm install dnscache` Usage ----- ```javascript var dns = require('dns'), dnscache = require('dnscache')({ "enable" : true, "ttl" : 300, "cachesize" : 1000 }); //to use the cached dns either of dnscache or dns can be called. //all the methods of dns are wrapped, this one just shows lookup on an example //will call the wrapped dns dnscache.lookup('www.yahoo.com', function(err, result) { //do something with result }); //will call the wrapped dns dns.lookup('www.google.com', function(err, result) { //do something with result }); ``` Configuration ------------- * `enable` - Whether dnscache is enabled or not, defaults to `false`. * `ttl` - ttl in seconds for cache-entries. Default: `300` * `cachesize` - number of cache entries, defaults to `1000` * `cache` - If a custom cache needs to be used instead of the supplied cache implementation. Only for Advanced Usage. Custom Cache needs to have same interface for `get` and `set`. Advanced Caching ---------------- If you want to use a different cache mechanism (ex: `mdbm`, `redis`), you only need to create an object similar to this: ```javascript var Cache = function(config) { this.set = function(key, value, callback) {}; this.get = function(key, callback) {}; }; ``` Build Status ------------ [![Build Status](https://secure.travis-ci.org/yahoo/dnscache.png?branch=master)](http://travis-ci.org/yahoo/dnscache) Node Badge ---------- [![NPM](https://nodei.co/npm/dnscache.png)](https://nodei.co/npm/dnscache/) package/examples/sample.js000755 0000002757 3560116604 012733 0ustar00000000 000000 /* * Copyright (c) 2013, Yahoo! Inc. All rights reserved. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ var dns = require('dns'), dnscache = require('../lib/index.js')({"enable" : true, "ttl" : 300, "cachesize" : 1000}), now = Date.now(), now1; //to use the cached dns either of dnscahe or dns can be called dnscache.lookup('www.yahoo.com', function(err, result) { console.log('lookup time to www.yahoo.com without cache: ', Date.now() - now); now = Date.now(); dnscache.lookup('www.yahoo.com', function(err, result) { console.log('lookup time to www.yahoo.com with cache: ', Date.now() - now); console.log(typeof dnscache.internalCache); }); }); now1 = Date.now(); //dns methods are wrapped too with the cached dns dns.lookup('www.google.com', function(err, result) { console.log('lookup time to www.google.com without cache: ', Date.now() - now1); now1 = Date.now(); dns.lookup('www.google.com', function(err, result) { console.log('lookup time to www.google.com with cache: ', Date.now() - now1); console.log(typeof dns.internalCache); }); }); /* Sample output on console.log * lookup time to www.yahoo.com without cache: 6 * lookup time to www.yahoo.com with cache: 1 * object * lookup time to www.google.com without cache: 8 * lookup time to www.google.com with cache: 1 * object */package/lib/cache.js000644 0000010031 3560116604 011422 0ustar00000000 000000 /* * Copyright (c) 2013, Yahoo! Inc. All rights reserved. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ var CacheObject = function (conf) { conf = conf || {}; conf.ttl = parseInt(conf.ttl, 10) || 300; //0 is not permissible conf.cachesize = parseInt(conf.cachesize, 10) || 1000; //0 is not permissible this.ttl = conf.ttl * 1000; this.max = conf.cachesize; this.count = 0; this.data = {}; var next = require('asap'); this.set = function (key, value, callback) { var self = this; next(function () { if (self.data[key]) { if (self.data[key].newer) { if (self.data[key].older) { self.data[key].newer.older = self.data[key].older; self.data[key].older.newer = self.data[key].newer; } else { self.tail = self.data[key].newer; delete self.tail.older; } self.data[key].older = self.head; self.head.newer = self.data[key]; delete self.data[key].newer; self.head = self.data[key]; } self.head.val = value; self.head.hit = 0; self.head.ts = Date.now(); } else { // key is not exist self.data[key] = { "key" : key, "val" : value, "hit" : 0, "ts" : Date.now() }; if (!self.head) { // cache is empty self.head = self.data[key]; self.tail = self.data[key]; } else { // insert the new entry to the front self.head.newer = self.data[key]; self.data[key].older = self.head; self.head = self.data[key]; } if (self.count >= self.max) { // remove the tail var temp = self.tail; self.tail = self.tail.newer; delete self.tail.next; delete self.data[temp.key]; } else { self.count = self.count + 1; } } /* jshint -W030 */ callback && callback(null, value); }); }; this.get = function (key, callback) { var self = this; if (!callback) { throw('cache.get callback is required.'); } next(function () { if (!self.data[key]) { return callback(null, undefined); } var value; if (conf.ttl !== 0 && (Date.now() - self.data[key].ts) >= self.ttl) { if (self.data[key].newer) { if (self.data[key].older) { // in the middle of the list self.data[key].newer.older = self.data[key].older; self.data[key].older.newer = self.data[key].newer; } else { // tail self.tail = self.data[key].newer; delete self.tail.older; } } else { // the first item if (self.data[key].older) { self.head = self.data[key].older; delete self.head.newer; } else { // 1 items delete self.head; delete self.tail; } } delete self.data[key]; self.count = self.count - 1; } else { self.data[key].hit = self.data[key].hit + 1; value = self.data[key].val; } callback(null, value); }); }; }; module.exports = CacheObject; package/lib/index.js000644 0000041153 3560116604 011477 0ustar00000000 000000 /* * Copyright (c) 2013, Yahoo! Inc. All rights reserved. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ var CacheObject = require('./cache.js'), deepCopy = require('lodash.clone'), dns = require('dns'); /* * Stores the list of callbacks waiting for a dns call. */ var callbackList = {}; // original function storage var backup_object = { lookup : dns.lookup, resolve : dns.resolve, resolve4 : dns.resolve4, resolve6 : dns.resolve6, resolveMx : dns.resolveMx, resolveTxt : dns.resolveTxt, resolveSrv : dns.resolveSrv, resolveNs : dns.resolveNs, resolveCname : dns.resolveCname, reverse : dns.reverse }; /* * Make a deep copy of the supplied object. This function reliably copies only * what is valid for a JSON object, array, or other element. */ var deepCopy = function (o) { var newArr, ix, newObj, prop; if (!o || typeof o !== 'object') { return o; } if (Array.isArray(o)) { newArr = []; for (ix = 0; ix < o.length; ix += 1) { newArr.push(deepCopy(o[ix])); } return newArr; } else { newObj = {}; for (prop in o) { /* istanbul ignore else */ if (o.hasOwnProperty(prop)) { newObj[prop] = deepCopy(o[prop]); } } return newObj; } }; // original function storage var EnhanceDns = function (conf) { conf = conf || {}; conf.ttl = parseInt(conf.ttl, 10) || 300; //0 is not allowed ie it ttl is set to 0, it will take the default conf.cachesize = parseInt(conf.cachesize, 10); //0 is allowed but it will disable the caching if (isNaN(conf.cachesize)) { conf.cachesize = 1000; //set default cache size to 1000 records max } if(!conf.enable || conf.cachesize <= 0) { delete dns.internalCache; for(var key in backup_object) { /* istanbul ignore else */ if (backup_object.hasOwnProperty(key)) { dns[key] = backup_object[key]; } } return dns; } // insert cache object to the instance if(!dns.internalCache) { dns.internalCache = conf.cache ? new conf.cache(conf) : new CacheObject(conf); callbackList = {}; } var cache = dns.internalCache; // override dns.lookup method dns.lookup = function (domain, options, callback) { var family = 0; var hints = 0; var all = false; if (arguments.length === 2) { callback = options; options = family; } else if (typeof options === 'object') { if (options.family) { family = +options.family; if (family !== 4 && family !== 6) { callback(new Error('invalid argument: `family` must be 4 or 6')); return; } } /*istanbul ignore next - "hints" require node 0.12+*/ if (options.hints) { hints = +options.hints; } all = (options.all === true); } else if (options) { family = +options; if (family !== 4 && family !== 6) { callback(new Error('invalid argument: `family` must be 4 or 6')); return; } } var key = 'lookup_' + domain + '_' + family + '_' + hints + '_' + all; cache.get(key, function (error, record) { if (record) { /*istanbul ignore next - "all" option require node 4+*/ if (Array.isArray(record)) { return callback(error, record); } return callback(error, record.address, record.family); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try{ backup_object.lookup(domain, options, function (err, address, family_r) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } var value; /*istanbul ignore next - "all" option require node 4+*/ if (Array.isArray(address)) { value = deepCopy(address); } else { value = { 'address' : address, 'family' : family_r }; } cache.set(key, value, function () { list.forEach(function (cb) { cb(err, address, family_r); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.resolve method dns.resolve = function (domain, type, callback) { var type_new, callback_new; if (typeof type === 'string') { type_new = type; callback_new = callback; } else { type_new = "A"; callback_new = type; } var key = 'resolve_' + domain + '_' + type_new; cache.get(key, function (error, record) { if (record) { return callback_new(error, deepCopy(record), true); } var list = callbackList[key] = callbackList[key] || []; list.push(callback_new); if (list.length > 1) { return; } try { backup_object.resolve(domain, type_new, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses), false); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback_new(err); } }); }; // override dns.resolve4 method dns.resolve4 = function (domain, callback) { var key = 'resolve4_' + domain; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try { backup_object.resolve4(domain, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.resolve6 method dns.resolve6 = function (domain, callback) { var key = 'resolve6_' + domain; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try { backup_object.resolve6(domain, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.resolveMx method dns.resolveMx = function (domain, callback) { var key = 'resolveMx_' + domain; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try { backup_object.resolveMx(domain, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.resolveTxt method dns.resolveTxt = function (domain, callback) { var key = 'resolveTxt_' + domain; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try { backup_object.resolveTxt(domain, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.resolveSrv method dns.resolveSrv = function (domain, callback) { var key = 'resolveSrv_' + domain; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); /* istanbul ignore if */ if (list.length > 1) { return; } try { backup_object.resolveSrv(domain, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.resolveNs method dns.resolveNs = function (domain, callback) { var key = 'resolveNs_' + domain; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try { backup_object.resolveNs(domain, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.resolveCname method dns.resolveCname = function (domain, callback) { var key = 'resolveCname_' + domain; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try { backup_object.resolveCname(domain, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; // override dns.reverse method dns.reverse = function (ip, callback) { var key = 'reverse_' + ip; cache.get(key, function (error, record) { if (record) { return callback(error, deepCopy(record)); } var list = callbackList[key] = callbackList[key] || []; list.push(callback); if (list.length > 1) { return; } try { backup_object.reverse(ip, function (err, addresses) { if (err) { list.forEach(function (cb) { cb(err); }); delete callbackList[key]; return; } cache.set(key, addresses, function () { list.forEach(function (cb) { cb(err, deepCopy(addresses)); }); delete callbackList[key]; }); }); } catch (err) { /*istanbul ignore next - doesn't throw in node 0.10*/ callback(err); } }); }; return dns; }; module.exports = function(conf) { return new EnhanceDns(conf); }; package/test/index.js000644 0000025453 3560116604 011715 0ustar00000000 000000 /*global describe, it*/ /* * Copyright (c) 2013, Yahoo! Inc. All rights reserved. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ var assert = require('assert'), async = require('async'), mod = require('../lib/index.js')({ enable: true, ttl: 300, cachesize: 1000 }); var dns = require('dns'); var methods = [dns.lookup, dns.resolve, dns.resolve4, dns.resolve6, dns.resolveMx,dns.resolveTxt, dns.resolveSrv, dns.resolveNs, dns.resolveCname, dns.reverse]; var params = ["www.yahoo.com", "www.google.com", "www.google.com", "ipv6.google.com", "yahoo.com", "google.com", "www.yahoo.com", "yahoo.com", "www.yahoo.com", "173.236.27.26"]; var prefix = ['lookup_', 'resolve_', 'resolve4_', 'resolve6_', 'resolveMx_', 'resolveTxt_', 'resolveSrv_', 'resolveNs_', 'resolveCname_', 'reverse_']; var suffix = ['_0_0_false', '_A', 'none', 'none', 'none', 'none', 'none', 'none', 'none', 'none']; // node v4+ dns.lookup support "all" option. var node_support_lookup_all = process.versions.node.split('.')[0] >= 4; // node v0.12+ dns.lookup support "hints" option. var node_support_lookup_hints = process.versions.node.split('.')[0] > 0 || process.versions.node.split('.')[1] >= 12; describe('dnscache main test suite', function() { this.timeout(10000); //dns queries are slow.. it('should export an Object', function () { assert.equal(typeof mod, 'object'); }); it('should verify internal cache is create for each call', function (done) { var index = 0; async.eachSeries(methods, function(method, cb) { method(params[index], function(err, result) { ++index; cb(err, result); }); }, function () { assert.ok(dns.internalCache); methods.forEach(function(name, index) { var key = suffix[index] !== 'none' ? prefix[index] + params[index] + suffix[index] : prefix[index] + params[index]; assert.ok(dns.internalCache.data[key], 'entry not there for ' + key); assert.equal(dns.internalCache.data[key].hit, 0, 'hit should be 0 for ' + key); }); done(); }); }); it('verify hits are incremented', function (done) { var index = 0; async.eachSeries(methods, function(method, cb) { method(params[index], function(err, result) { ++index; cb(err, result); }); }, function () { assert.ok(dns.internalCache); methods.forEach(function(name, index) { var key = suffix[index] !== 'none' ? prefix[index] + params[index] + suffix[index] : prefix[index] + params[index]; assert.ok(dns.internalCache.data[key], 'entry not there for ' + key); assert.equal(dns.internalCache.data[key].hit, 1, 'hit should be 1 for ' + key); }); done(); }); }); it('require again and verify cache is same as before', function (done) { require('../lib/index.js')({ enable: true, ttl: 300, cachesize: 1000 }); assert.ok(dns.internalCache); methods.forEach(function(name, index) { var key = suffix[index] !== 'none' ? prefix[index] + params[index] + suffix[index] : prefix[index] + params[index]; assert.ok(dns.internalCache.data[key], 'entry not there for ' + key); assert.equal(dns.internalCache.data[key].hit, 1, 'hit should be 1 for ' + key); }); done(); }); it('should verify family4/family6 cache is created for local addresses', function (done) { dns.lookup('127.0.0.1', 4, function() { dns.lookup('::1', 6, function() { dns.lookup('127.0.0.1', { family: 4, hints: dns.ADDRCONFIG }, function() { dns.lookup('127.0.0.1', { hints: dns.ADDRCONFIG }, function() { dns.lookup('::1', { family: 6, hints: dns.ADDRCONFIG }, function() { assert.ok(dns.internalCache); var hit = node_support_lookup_hints ? 0 : 1; assert.equal(dns.internalCache.data['lookup_127.0.0.1_4_0_false'].hit, hit, 'hit should be ' + hit + ' for family4'); assert.equal(dns.internalCache.data['lookup_::1_6_0_false'].hit, hit, 'hit should be ' + hit + ' for family6'); done(); }); }); }); }); }); }); it('should error if the underlying dns method throws', function(done) { var errors = []; async.each(methods, function(method, cb) { method([], function(err) { errors.push(err); cb(null); }); }, function (err) { assert.ok(!err); assert.ok(Array.isArray(errors)); assert.ok(errors.length > 0); errors.forEach(function(e) { if (e) { //one node 0.10 method doens't throw assert.ok((e instanceof Error)); } }); done(); }); }); it('should error on invalid reverse lookup', function(done) { // from the TEST-NET-1 block, as specified in https://tools.ietf.org/html/rfc5737 dns.reverse('192.0.2.0', function(err) { assert.ok((err instanceof Error)); done(); }); }); it('should error on invalid family', function(done) { dns.lookup('127.0.0.1', 7, function(err) { assert.ok((err instanceof Error)); done(); }); }); it('should error on invalid family Object', function() { dns.lookup('127.0.0.1', { family: 7 }, function(err) { assert.ok((err instanceof Error)); }); }); it('should create resolve cache with type', function (done) { dns.resolve('www.yahoo.com', 'A', function(err, result) { assert.ok(dns.internalCache); assert.ok(result); assert.equal(dns.internalCache.data['resolve_www.yahoo.com_A'].hit, 0, 'hit should be 0 for resolve'); done(); }); }); it('not create a cache from an error in a lookup', function (done) { var index = 0; async.eachSeries(methods, function(method, cb) { method('someerrordata', function(err) { ++index; cb(null, err); }); }, function () { methods.forEach(function(name, index) { var key = suffix[index] !== 'none' ? prefix[index] + 'someerrordata' + suffix[index] : prefix[index] + 'someerrordata'; assert.equal(dns.internalCache.data[key], undefined, 'entry should not there for ' + key); }); done(); }); }); it('should not cache by default', function(done) { //if created from other tests if (require('dns').internalCache) { delete require('dns').internalCache; } var testee = require('../lib/index.js')(); testee.lookup('127.0.0.1', function() { assert.ok(!dns.internalCache); assert.ok(!require('dns').internalCache); done(); }); }); it('should not cache if enabled: false', function(done) { //if created from other tests if (require('dns').internalCache) { delete require('dns').internalCache; } var testee = require('../lib/index.js')({ enable: false }); testee.lookup('127.0.0.1', function() { assert.ok(!testee.internalCache); assert.ok(!require('dns').internalCache); done(); }); }); it('should not cache if cachsize is 0', function(done) { //if created from other tests if (require('dns').internalCache) { delete require('dns').internalCache; } var testee = require('../lib/index.js')({ enable: true, cachesize: 0 }); testee.lookup('127.0.0.1', function() { assert.ok(!testee.internalCache); assert.ok(!require('dns').internalCache); done(); }); }); it('should create a cache with default settings', function(done) { //if created from other tests if (require('dns').internalCache) { delete require('dns').internalCache; } var conf = { enable: true }, testee = require('../lib/index.js')(conf); testee.lookup('127.0.0.1', function() { //verify cache is created assert.ok(testee.internalCache); assert.equal(testee.internalCache.data['lookup_127.0.0.1_0_0_false'].hit, 0, 'hit should be 0 for family4'); assert.ok(dns.internalCache); assert.equal(dns.internalCache.data['lookup_127.0.0.1_0_0_false'].hit, 0, 'hit should be 0 for family4'); //verify default values assert.ok(conf); assert.equal(conf.ttl, 300); assert.equal(conf.cachesize, 1000); done(); }); }); // lookup's all option require node v4+. if (node_support_lookup_all) { it('should return array if lookup all', function(done) { //if created from other tests if (require('dns').internalCache) { delete require('dns').internalCache; } var conf = { enable: true }, testee = require('../lib/index.js')(conf); dns.lookup('127.0.0.1', {all: true}, function(err, addresses) { assert.ok(Array.isArray(addresses)); assert.equal(testee.internalCache.data['lookup_127.0.0.1_0_0_true'].hit, 0, 'hit should be 0'); dns.lookup('127.0.0.1', {all: true}, function(err, addresses) { assert.ok(Array.isArray(addresses)); assert.equal(testee.internalCache.data['lookup_127.0.0.1_0_0_true'].hit, 1, 'hit should be 1'); done(); }); }); }); it('should return an array copy if lookup all', function(done) { //if created from other tests if (require('dns').internalCache) { delete require('dns').internalCache; } var conf = { enable: true }, testee = require('../lib/index.js')(conf); dns.lookup('127.0.0.1', {all: true}, function(err, addresses) { assert.ok(Array.isArray(addresses)); addresses.pop(); assert.equal(testee.internalCache.data['lookup_127.0.0.1_0_0_true'].val.length, 1, 'length should be 1'); done(); }); }); } }); package/test/testcache.js000644 0000016142 3560116604 012544 0ustar00000000 000000 /*jshint newcap: false */ /*global describe, it*/ /* * Copyright (c) 2013, Yahoo! Inc. All rights reserved. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ var assert = require('assert'), async = require('async'), Mod_cache = require('../lib/cache.js'); describe('caching tests', function() { it('should return a Cache Object with defaults', function() { var conf = {}; new Mod_cache(conf); assert.ok(conf); assert.equal(conf.ttl, 300); assert.equal(conf.cachesize, 1000); }); it('should return a Cache Object with defaults without config', function() { var mod = new Mod_cache(); assert.equal(mod.ttl, 300 * 1000); assert.equal(mod.max, 1000); }); it('should cache entries for lru', function(done) { var CacheObject = new Mod_cache({"ttl" : 300, "cachesize" : 5}), array = Array.apply(null, Array(5)).map(function(v, k) { return k; }); async.each(array, function (name, callback) { CacheObject.set(name, name, function () { callback(null); }); }, function () { CacheObject.get(0, function (err0) { CacheObject.get(1, function (err1, data1) { CacheObject.get('unknownkey', function (err2, data2) { assert.equal(true, data1 !== undefined && data2 === undefined); assert.equal(null, err0); assert.equal(null, err1); assert.equal(null, err2); done(); }); }); }); }); }); it('should update multiple keys', function(done) { var CacheObject = new Mod_cache({ ttl: 300, cachesize: 5 }); CacheObject.set(1, 1, function () { CacheObject.set(2, 2, function () { CacheObject.set(3, 30, function () { CacheObject.set(3, 31, function () { CacheObject.set(2, 4, function () { CacheObject.set(2, 5, function () { CacheObject.set(1, 6, function () { assert.equal(CacheObject.count, 3); assert.equal(CacheObject.tail.key, 3); assert.equal(CacheObject.tail.val, 31); assert.equal(CacheObject.head.key, 1); assert.equal(CacheObject.head.val, 6); done(); }); }); }); }); }); }); }); }); it('should get a key', function(done) { var CacheObject = new Mod_cache({ ttl: 300, cachesize: 5 }); CacheObject.set(1, 1, function () { CacheObject.set(1, 2, function () { CacheObject.get(1, function (err, rec) { assert.equal(rec, 2); done(); }); }); }); }); it('should allow to see hit stats', function(done) { var CacheObject = new Mod_cache({ ttl: 300, cachesize: 5 }); CacheObject.set(1, 1, function () { CacheObject.get(1, function () { CacheObject.get(1, function () { assert.equal(CacheObject.data['1'].hit, 2); done(); }); }); }); }); it('should expire a single item', function(done) { var CacheObject = new Mod_cache({ ttl: 1, cachesize: 5 }), noop = function() {}; CacheObject.set(1, 1); CacheObject.get(1, noop); CacheObject.get(1, noop); setTimeout(function(){ CacheObject.get(1, function () { assert.equal(undefined, CacheObject.data['1']); done(); }); }, 1200); }); it('should not expire a key', function(done) { var CacheObject = new Mod_cache({ ttl: 0, cachesize: 5 }); CacheObject.set(2, 2); CacheObject.set(1, 1); setTimeout(function(){ CacheObject.get(1, function () { assert.ok(CacheObject.data['1']); assert.equal(CacheObject.data['1'].val, 1); done(); }); }, 1200); }); it('should expire head via ttl', function(done) { var CacheObject = new Mod_cache({ ttl: 1, cachesize: 5 }); CacheObject.set(2, 2); CacheObject.set(1, 1); setTimeout(function(){ CacheObject.get(1, function () { assert.equal(undefined, CacheObject.data['1']); done(); }); }, 1200); }); it('should expire tail via ttl', function(done) { var CacheObject = new Mod_cache({ ttl: 1, cachesize: 5 }); CacheObject.set(1, 1); CacheObject.set(2, 2); setTimeout(function(){ CacheObject.get(1, function () { assert.equal(undefined, CacheObject.data['1']); done(); }); }, 1200); }); it('should expire middle via ttl', function(done) { var CacheObject = new Mod_cache({ ttl: 1, cachesize: 5 }); CacheObject.set(3, 3); CacheObject.set(1, 1); CacheObject.set(2, 2); setTimeout(function(){ CacheObject.get(1, function () { assert.equal(undefined, CacheObject.data['1']); done(); }); }, 1200); }); it('should throw if cache.get does not get a callback', function() { var CacheObject = new Mod_cache({ ttl: 1, cachesize: 5 }); assert.throws(function() { CacheObject.get(1); }, /callback is required/); }); it('should remove items from cache when cache limit is hit', function(done) { var CacheObject = new Mod_cache({ ttl: 1, cachesize: 2 }); CacheObject.set(1, 1, function() { assert.equal(CacheObject.count, 1); CacheObject.set(2, 1, function() { assert.equal(CacheObject.count, 2); CacheObject.set(3, 1, function() { assert.equal(CacheObject.count, 2); done(); }); }); }); }); it('should not error when calling cache.get on an expired key twice in the same tick', function(done) { var CacheObject = new Mod_cache({ ttl: 1, cachesize: 5 }); CacheObject.set(1, 1); setTimeout(function() { CacheObject.get(1, Function.prototype); CacheObject.get(1, function() { done(); }); }, 1200); }); }); package/test/testcoverage.js000644 0000004233 3560116604 013272 0ustar00000000 000000 /*global describe, it*/ var globalOptions = { enable: true, ttl: 300, cachesize: 1000 }; var assert = require("assert"); var async = require("async"); var dns = require('../lib/index.js')(globalOptions); function prepare(options) { if (!options) { options = globalOptions; } //if created from other tests if (dns.internalCache) { delete dns.internalCache; } dns = require("../lib/index.js")(options); return dns; } describe('dnscache additional tests for full coverage', function() { this.timeout(10000); //dns queries are slow.. it("should convert family string to number", function(done) { prepare(); dns.lookup("127.0.0.1", "4", function() { dns.lookup("127.0.0.1", "6", function() { done(); }); }); }); it("coverage: options is undefined", function(done) { prepare(); dns.lookup("127.0.0.1", undefined, function() { done(); }); }); it("coverage: custom cache object", function(done) { var cacheClass = require("../lib/cache.js"); prepare({enable: true, cache: cacheClass}); done(); }); it("coverage: simultaneous requests", function(done) { prepare(); var methods = [ ["lookup", "127.0.0.1"], ["resolve", "localhost"], ["resolve4", "localhost"], ["resolve6", "localhost"], ["resolveMx", "yahoo.com"], ["resolveTxt", "yahoo.com"], ["resolveNs", "yahoo.com"], ["resolveCname", "www.yahoo.com"], ["reverse", "1.1.1.1"] ]; async.eachSeries(methods, function(method, itemDone) { async.times(2, function(i, callback) { dns[method[0]](method[1], function(err, result) { callback(null, result); }); }, function(err, results) { (assert.deepStrictEqual || assert.deepEqual)(results[0], results[1], "expected same result from cached query"); itemDone(null); }); }, function() { done(); }); }); });