Skip to content

Commit 80528cc

Browse files
authored
refactor(dnshttpclient): use async function instead of Promise (#2774)
1 parent fe9e956 commit 80528cc

6 files changed

Lines changed: 85 additions & 60 deletions

File tree

config/config.default.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ module.exports = appInfo => {
233233
* The option for httpclient
234234
* @member Config#httpclient
235235
* @property {Boolean} enableDNSCache - Enable DNS lookup from local cache or not, default is false.
236+
* @property {Boolean} dnsCacheLookupInterval - minimum interval of DNS query on the same hostname (default 10s).
236237
*
237238
* @property {Number} request.timeout - httpclient request default timeout, default is 5000 ms.
238239
*
@@ -248,8 +249,8 @@ module.exports = appInfo => {
248249
*/
249250
config.httpclient = {
250251
enableDNSCache: false,
252+
dnsCacheLookupInterval: 10000,
251253
dnsCacheMaxLength: 1000,
252-
dnsCacheMaxAge: 10000,
253254

254255
request: {
255256
timeout: 5000,

lib/core/dnscache_httpclient.js

Lines changed: 47 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
'use strict';
22

3-
const dns = require('dns');
3+
const dns = require('mz/dns');
44
const LRU = require('ylru');
55
const urlparse = require('url').parse;
66
const HttpClient = require('./httpclient');
77
const utility = require('utility');
88

99
const IP_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
10+
const DNSLOOKUP = Symbol('DNSCacheHttpClient#dnslookup');
11+
const UPDATE_DNS = Symbol('DNSCacheHttpClient#updateDNS');
1012

1113
class DNSCacheHttpClient extends HttpClient {
1214
constructor(app) {
@@ -24,20 +26,22 @@ class DNSCacheHttpClient extends HttpClient {
2426

2527
// the callback style
2628
if (callback) {
27-
this._dnsLookup(url, args, (err, result) => {
28-
if (err) return callback(err);
29-
super.request(result.url, result.args, callback);
30-
});
29+
this.app.deprecate('[dnscache_httpclient] Please don\'t use callback when `request()`');
30+
31+
this[DNSLOOKUP](url, args)
32+
.then(result => {
33+
return super.request(result.url, result.args);
34+
})
35+
.then(result => process.nextTick(() => callback(null, result.data, result.res)))
36+
.catch(err => process.nextTick(() => callback(err)));
3137
return;
3238
}
3339

3440
// the Promise style
35-
return new Promise((resolve, reject) => {
36-
this._dnsLookup(url, args, (err, result) => {
37-
if (err) return reject(err);
38-
resolve(result);
39-
});
40-
}).then(result => super.request(result.url, result.args));
41+
return (async () => {
42+
const result = await this[DNSLOOKUP](url, args);
43+
return super.request(result.url, result.args);
44+
})();
4145
}
4246

4347
curl(url, args, callback) {
@@ -61,14 +65,14 @@ class DNSCacheHttpClient extends HttpClient {
6165
};
6266
}
6367

64-
_dnsLookup(url, args, callback) {
68+
async [DNSLOOKUP](url, args) {
6569
const parsed = typeof url === 'string' ? urlparse(url) : url;
6670
// hostname must exists
6771
const hostname = parsed.hostname;
6872

6973
// don't lookup when hostname is IP
7074
if (hostname && IP_REGEX.test(hostname)) {
71-
return callback(null, { url, args });
75+
return { url, args };
7276
}
7377

7478
args = args || {};
@@ -82,56 +86,45 @@ class DNSCacheHttpClient extends HttpClient {
8286
const record = this.dnsCache.get(hostname);
8387
const now = Date.now();
8488
if (record) {
85-
const needUpdate = now - record.timestamp >= this.dnsCacheLookupInterval;
86-
if (needUpdate) {
89+
if (now - record.timestamp >= this.dnsCacheLookupInterval) {
8790
// make sure next request don't refresh dns query
8891
record.timestamp = now;
89-
}
90-
callback(null, {
91-
url: this._formatDnsLookupUrl(hostname, url, record.ip),
92-
args,
93-
});
94-
if (!needUpdate) {
95-
// no need to refresh dns record
96-
return;
97-
}
98-
// make sure not callback twice
99-
callback = null;
100-
}
101-
102-
dns.lookup(hostname, { family: 4 }, (err, address) => {
103-
const logger = args.ctx ? args.ctx.coreLogger : this.app.coreLogger;
104-
if (err) {
105-
logger.warn('[dnscache_httpclient] dns lookup error: %s(%s) => %s', hostname, url, err);
106-
// no cache, return error
107-
return callback && callback(err);
92+
this.app.runInBackground(async () => {
93+
await this[UPDATE_DNS](hostname, args);
94+
});
10895
}
10996

110-
logger.info('[dnscache_httpclient] dns lookup success: %s(%s) => %s', hostname, url, address);
111-
this.dnsCache.set(hostname, {
112-
timestamp: Date.now(),
113-
ip: address,
114-
});
97+
return { url: formatDnsLookupUrl(hostname, url, record.ip), args };
98+
}
11599

116-
callback && callback(null, {
117-
url: this._formatDnsLookupUrl(hostname, url, address),
118-
args,
119-
});
120-
});
100+
const address = await this[UPDATE_DNS](hostname, args);
101+
return { url: formatDnsLookupUrl(hostname, url, address), args };
121102
}
122103

123-
_formatDnsLookupUrl(host, url, address) {
124-
if (typeof url === 'string') {
125-
url = url.replace(host, address);
126-
} else {
127-
url = utility.assign({}, url);
128-
url.hostname = url.hostname.replace(host, address);
129-
if (url.host) {
130-
url.host = url.host.replace(host, address);
131-
}
104+
async [UPDATE_DNS](hostname, args) {
105+
const logger = args.ctx ? args.ctx.coreLogger : this.app.coreLogger;
106+
try {
107+
const [ address ] = await dns.lookup(hostname, { family: 4 });
108+
logger.info('[dnscache_httpclient] dns lookup success: %s => %s',
109+
hostname, address);
110+
this.dnsCache.set(hostname, { timestamp: Date.now(), ip: address });
111+
return address;
112+
} catch (err) {
113+
err.message = `[dnscache_httpclient] dns lookup error: ${hostname} => ${err.message}`;
114+
throw err;
132115
}
133-
return url;
134116
}
117+
135118
}
136119

137120
module.exports = DNSCacheHttpClient;
121+
122+
function formatDnsLookupUrl(host, url, address) {
123+
if (typeof url === 'string') return url.replace(host, address);
124+
const urlObj = utility.assign({}, url);
125+
urlObj.hostname = urlObj.hostname.replace(host, address);
126+
if (urlObj.host) {
127+
urlObj.host = urlObj.host.replace(host, address);
128+
}
129+
return urlObj;
130+
}

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"delegates": "^1.0.0",
2727
"egg-cluster": "^1.18.0",
2828
"egg-cookies": "^2.2.2",
29-
"egg-core": "^4.8.0",
29+
"egg-core": "^4.9.1",
3030
"egg-development": "^2.3.1",
3131
"egg-i18n": "^2.0.0",
3232
"egg-jsonp": "^2.0.0",
@@ -48,6 +48,7 @@
4848
"koa-is-json": "^1.0.0",
4949
"koa-override": "^3.0.0",
5050
"ms": "^2.1.1",
51+
"mz": "^2.7.0",
5152
"on-finished": "^2.3.0",
5253
"sendmessage": "^1.1.0",
5354
"urllib": "^2.29.0",
@@ -61,8 +62,8 @@
6162
"coffee": "^4.1.0",
6263
"egg-alinode": "^1.0.3",
6364
"egg-bin": "^4.7.1",
64-
"egg-doctools": "^2.3.1",
65-
"egg-mock": "^3.17.2",
65+
"egg-doctools": "^2.4.0",
66+
"egg-mock": "^3.17.3",
6667
"egg-plugin-puml": "^2.4.0",
6768
"egg-tracer": "^1.1.0",
6869
"egg-view-nunjucks": "^2.2.0",

test/app/extend/request.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ describe('test/app/extend/request.test.js', () => {
173173
}
174174

175175
it('should get string value', () => {
176+
expectQuery('=b', {});
176177
expectQuery('a=b', { a: 'b' });
177178
expectQuery('a=&', { a: '' });
178179
expectQuery('a=b&', { a: 'b' });

test/fixtures/apps/dnscache_httpclient/config/config.default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
exports.httpclient = {
44
enableDNSCache: true,
5+
dnsCacheLookupInterval: 5000,
56
};
67

78
exports.keys = 'test key';

test/lib/core/dnscache_httpclient.test.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
const mm = require('egg-mock');
44
const assert = require('assert');
5-
const dns = require('dns');
5+
const dns = require('mz/dns');
66
const urlparse = require('url').parse;
7+
const sleep = require('mz-modules/sleep');
78
const utils = require('../../utils');
89

910
describe('test/lib/core/dnscache_httpclient.test.js', () => {
@@ -130,7 +131,7 @@ describe('test/lib/core/dnscache_httpclient.test.js', () => {
130131
});
131132

132133
it('should dnsCacheMaxLength work', async () => {
133-
mm.data(dns, 'lookup', '127.0.0.1');
134+
mm(dns, 'lookup', async () => [ '127.0.0.1' ]);
134135

135136
// reset lru cache
136137
mm(app.httpclient.dnsCache, 'max', 1);
@@ -154,6 +155,33 @@ describe('test/lib/core/dnscache_httpclient.test.js', () => {
154155
assert(app.httpclient.dnsCache.get('another.com'));
155156
});
156157

158+
it('should cache and update', async () => {
159+
mm(dns, 'lookup', async () => [ '127.0.0.1' ]);
160+
161+
let obj = urlparse(url + '/get_headers');
162+
let result = await app.curl(obj, { dataType: 'json' });
163+
assert(result.status === 200);
164+
assert(result.data.host === host);
165+
let record = app.httpclient.dnsCache.get('localhost');
166+
const timestamp = record.timestamp;
167+
assert(record);
168+
169+
obj = urlparse(url + '/get_headers');
170+
result = await app.curl(obj, { dataType: 'json' });
171+
assert(result.status === 200);
172+
assert(result.data.host === host);
173+
record = app.httpclient.dnsCache.get('localhost');
174+
assert(timestamp === record.timestamp);
175+
176+
await sleep(5000);
177+
obj = urlparse(url + '/get_headers');
178+
result = await app.curl(obj, { dataType: 'json' });
179+
assert(result.status === 200);
180+
assert(result.data.host === host);
181+
record = app.httpclient.dnsCache.get('localhost');
182+
assert(timestamp !== record.timestamp);
183+
});
184+
157185
it('should not cache ip', async () => {
158186
const obj = urlparse(url.replace('localhost', '127.0.0.1') + '/get_headers');
159187
const result = await app.curl(obj, { dataType: 'json' });

0 commit comments

Comments
 (0)