123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- 'use strict';
- const dns = require('mz/dns');
- const LRU = require('ylru');
- const HttpClient = require('./httpclient');
- const utility = require('utility');
- const utils = require('./utils');
- const IP_REGEX = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
- const DNSLOOKUP = Symbol('DNSCacheHttpClient#dnslookup');
- const UPDATE_DNS = Symbol('DNSCacheHttpClient#updateDNS');
- class DNSCacheHttpClient extends HttpClient {
- constructor(app) {
- super(app);
- this.dnsCacheLookupInterval = this.app.config.httpclient.dnsCacheLookupInterval;
- this.dnsCache = new LRU(this.app.config.httpclient.dnsCacheMaxLength);
- }
- request(url, args, callback) {
- // request(url, callback)
- if (typeof args === 'function') {
- callback = args;
- args = null;
- }
- // the callback style
- if (callback) {
- this.app.deprecate('[dnscache_httpclient] We now support async for this function, so callback isn\'t recommended.');
- // disable dns cache in request by args handle
- if (args && args.enableDNSCache === false) {
- super.request(url, args, callback);
- return;
- }
- this[DNSLOOKUP](url, args)
- .then(result => {
- return super.request(result.url, result.args);
- })
- .then(result => process.nextTick(() => callback(null, result.data, result.res)))
- .catch(err => process.nextTick(() => callback(err)));
- return;
- }
- // the Promise style
- return (async () => {
- // disable dns cache in request by args handle
- if (args && args.enableDNSCache === false) {
- return super.request(url, args);
- }
- const result = await this[DNSLOOKUP](url, args);
- return super.request(result.url, result.args);
- })();
- }
- async [DNSLOOKUP](url, args) {
- let parsed;
- if (typeof url === 'string') {
- parsed = utils.safeParseURL(url);
- // invalid url or relative url
- if (!parsed) return { url, args };
- } else {
- parsed = url;
- }
- // hostname must exists
- const hostname = parsed.hostname;
- // don't lookup when hostname is IP
- if (hostname && IP_REGEX.test(hostname)) {
- return { url, args };
- }
- args = args || {};
- args.headers = args.headers || {};
- // set when host header doesn't exist
- if (!args.headers.host && !args.headers.Host) {
- // host must combine with hostname:port, node won't use `parsed.host`
- args.headers.host = parsed.port ? `${hostname}:${parsed.port}` : hostname;
- }
- const record = this.dnsCache.get(hostname);
- const now = Date.now();
- if (record) {
- if (now - record.timestamp >= this.dnsCacheLookupInterval) {
- // make sure the next request doesn't refresh dns query
- record.timestamp = now;
- this[UPDATE_DNS](hostname, args).catch(err => this.app.emit('error', err));
- }
- return { url: formatDnsLookupUrl(hostname, url, record.ip), args };
- }
- const address = await this[UPDATE_DNS](hostname, args);
- return { url: formatDnsLookupUrl(hostname, url, address), args };
- }
- async [UPDATE_DNS](hostname, args) {
- const logger = args.ctx ? args.ctx.coreLogger : this.app.coreLogger;
- try {
- const [ address ] = await dns.lookup(hostname, { family: 4 });
- logger.info('[dnscache_httpclient] dns lookup success: %s => %s',
- hostname, address);
- this.dnsCache.set(hostname, { timestamp: Date.now(), ip: address });
- return address;
- } catch (err) {
- err.message = `[dnscache_httpclient] dns lookup error: ${hostname} => ${err.message}`;
- throw err;
- }
- }
- }
- module.exports = DNSCacheHttpClient;
- function formatDnsLookupUrl(host, url, address) {
- if (typeof url === 'string') return url.replace(host, address);
- const urlObj = utility.assign({}, url);
- urlObj.hostname = urlObj.hostname.replace(host, address);
- if (urlObj.host) {
- urlObj.host = urlObj.host.replace(host, address);
- }
- return urlObj;
- }
|