dnscache_httpclient.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. 'use strict';
  2. const dns = require('mz/dns');
  3. const LRU = require('ylru');
  4. const HttpClient = require('./httpclient');
  5. const utility = require('utility');
  6. const utils = require('./utils');
  7. const IP_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
  8. const DNSLOOKUP = Symbol('DNSCacheHttpClient#dnslookup');
  9. const UPDATE_DNS = Symbol('DNSCacheHttpClient#updateDNS');
  10. class DNSCacheHttpClient extends HttpClient {
  11. constructor(app) {
  12. super(app);
  13. this.dnsCacheLookupInterval = this.app.config.httpclient.dnsCacheLookupInterval;
  14. this.dnsCache = new LRU(this.app.config.httpclient.dnsCacheMaxLength);
  15. }
  16. request(url, args, callback) {
  17. // request(url, callback)
  18. if (typeof args === 'function') {
  19. callback = args;
  20. args = null;
  21. }
  22. // the callback style
  23. if (callback) {
  24. this.app.deprecate('[dnscache_httpclient] We now support async for this function, so callback isn\'t recommended.');
  25. // disable dns cache in request by args handle
  26. if (args && args.enableDNSCache === false) {
  27. super.request(url, args, callback);
  28. return;
  29. }
  30. this[DNSLOOKUP](url, args)
  31. .then(result => {
  32. return super.request(result.url, result.args);
  33. })
  34. .then(result => process.nextTick(() => callback(null, result.data, result.res)))
  35. .catch(err => process.nextTick(() => callback(err)));
  36. return;
  37. }
  38. // the Promise style
  39. return (async () => {
  40. // disable dns cache in request by args handle
  41. if (args && args.enableDNSCache === false) {
  42. return super.request(url, args);
  43. }
  44. const result = await this[DNSLOOKUP](url, args);
  45. return super.request(result.url, result.args);
  46. })();
  47. }
  48. async [DNSLOOKUP](url, args) {
  49. let parsed;
  50. if (typeof url === 'string') {
  51. parsed = utils.safeParseURL(url);
  52. // invalid url or relative url
  53. if (!parsed) return { url, args };
  54. } else {
  55. parsed = url;
  56. }
  57. // hostname must exists
  58. const hostname = parsed.hostname;
  59. // don't lookup when hostname is IP
  60. if (hostname && IP_REGEX.test(hostname)) {
  61. return { url, args };
  62. }
  63. args = args || {};
  64. args.headers = args.headers || {};
  65. // set when host header doesn't exist
  66. if (!args.headers.host && !args.headers.Host) {
  67. // host must combine with hostname:port, node won't use `parsed.host`
  68. args.headers.host = parsed.port ? `${hostname}:${parsed.port}` : hostname;
  69. }
  70. const record = this.dnsCache.get(hostname);
  71. const now = Date.now();
  72. if (record) {
  73. if (now - record.timestamp >= this.dnsCacheLookupInterval) {
  74. // make sure the next request doesn't refresh dns query
  75. record.timestamp = now;
  76. this[UPDATE_DNS](hostname, args).catch(err => this.app.emit('error', err));
  77. }
  78. return { url: formatDnsLookupUrl(hostname, url, record.ip), args };
  79. }
  80. const address = await this[UPDATE_DNS](hostname, args);
  81. return { url: formatDnsLookupUrl(hostname, url, address), args };
  82. }
  83. async [UPDATE_DNS](hostname, args) {
  84. const logger = args.ctx ? args.ctx.coreLogger : this.app.coreLogger;
  85. try {
  86. const [ address ] = await dns.lookup(hostname, { family: 4 });
  87. logger.info('[dnscache_httpclient] dns lookup success: %s => %s',
  88. hostname, address);
  89. this.dnsCache.set(hostname, { timestamp: Date.now(), ip: address });
  90. return address;
  91. } catch (err) {
  92. err.message = `[dnscache_httpclient] dns lookup error: ${hostname} => ${err.message}`;
  93. throw err;
  94. }
  95. }
  96. }
  97. module.exports = DNSCacheHttpClient;
  98. function formatDnsLookupUrl(host, url, address) {
  99. if (typeof url === 'string') return url.replace(host, address);
  100. const urlObj = utility.assign({}, url);
  101. urlObj.hostname = urlObj.hostname.replace(host, address);
  102. if (urlObj.host) {
  103. urlObj.host = urlObj.host.replace(host, address);
  104. }
  105. return urlObj;
  106. }