mirror of https://github.com/grpc/grpc-node.git
Fix DNS result handling, special case localhost, resolve comment
This commit is contained in:
parent
026d681a84
commit
b7656e0644
|
|
@ -158,7 +158,7 @@ export class ChannelImplementation implements Channel {
|
|||
);
|
||||
},
|
||||
};
|
||||
// TODO: check channel arg for default service config
|
||||
// TODO(murgatroid99): check channel arg for default service config
|
||||
const defaultServiceConfig: ServiceConfig = {
|
||||
loadBalancingConfig: [],
|
||||
methodConfig: [],
|
||||
|
|
|
|||
|
|
@ -69,15 +69,46 @@ const DEFAULT_PORT = '443';
|
|||
*/
|
||||
const IPV6_SUPPORT_RANGE = '>= 12.6';
|
||||
|
||||
const resolve4Promise = util.promisify(dns.resolve4);
|
||||
const resolve6Promise = util.promisify(dns.resolve6);
|
||||
/**
|
||||
* Get a promise that always resolves with either the result of the function
|
||||
* or the error if it failed.
|
||||
* @param fn
|
||||
*/
|
||||
function resolvePromisify<TArg, TResult, TError>(
|
||||
fn: (
|
||||
arg: TArg,
|
||||
callback: (error: TError | null, result: TResult) => void
|
||||
) => void
|
||||
): (arg: TArg) => Promise<TResult | TError> {
|
||||
return arg =>
|
||||
new Promise<TResult | TError>((resolve, reject) => {
|
||||
fn(arg, (error, result) => {
|
||||
if (error) {
|
||||
resolve(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const resolve4Promise = resolvePromisify<
|
||||
string,
|
||||
string[],
|
||||
NodeJS.ErrnoException
|
||||
>(dns.resolve4);
|
||||
const resolve6Promise = resolvePromisify<
|
||||
string,
|
||||
string[],
|
||||
NodeJS.ErrnoException
|
||||
>(dns.resolve6);
|
||||
|
||||
/**
|
||||
* Attempt to parse a target string as an IP address
|
||||
* @param target
|
||||
* @return An "IP:port" string if parsing was successful, `null` otherwise
|
||||
* @return An "IP:port" string in an array if parsing was successful, `null` otherwise
|
||||
*/
|
||||
function parseIP(target: string): string | null {
|
||||
function parseIP(target: string): string[] | null {
|
||||
/* These three regular expressions are all mutually exclusive, so we just
|
||||
* want the first one that matches the target string, if any do. */
|
||||
const match =
|
||||
|
|
@ -94,7 +125,7 @@ function parseIP(target: string): string | null {
|
|||
} else {
|
||||
port = DEFAULT_PORT;
|
||||
}
|
||||
return `${addr}:${port}`;
|
||||
return [`${addr}:${port}`];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,15 +152,20 @@ function mergeArrays<T>(...arrays: T[][]): T[] {
|
|||
* Resolver implementation that handles DNS names and IP addresses.
|
||||
*/
|
||||
class DnsResolver implements Resolver {
|
||||
private readonly ipResult: string | null;
|
||||
private readonly ipResult: string[] | null;
|
||||
private readonly dnsHostname: string | null;
|
||||
private readonly port: string | null;
|
||||
/* The promise results here contain, in order, the A record, the AAAA record,
|
||||
* and either the TXT record or an error if TXT resolution failed */
|
||||
pendingResultPromise: Promise<
|
||||
[string[], string[], string[][] | Error]
|
||||
private pendingResultPromise: Promise<
|
||||
[
|
||||
string[] | NodeJS.ErrnoException,
|
||||
string[] | NodeJS.ErrnoException,
|
||||
string[][] | NodeJS.ErrnoException
|
||||
]
|
||||
> | null = null;
|
||||
percentage: number;
|
||||
private percentage: number;
|
||||
private defaultResolutionError: StatusObject;
|
||||
constructor(private target: string, private listener: ResolverListener) {
|
||||
this.ipResult = parseIP(target);
|
||||
const dnsMatch = DNS_REGEX.exec(target);
|
||||
|
|
@ -143,8 +179,21 @@ class DnsResolver implements Resolver {
|
|||
} else {
|
||||
this.port = DEFAULT_PORT;
|
||||
}
|
||||
if (this.dnsHostname === 'localhost') {
|
||||
if (semver.satisfies(process.version, IPV6_SUPPORT_RANGE)) {
|
||||
this.ipResult = [`::1:${this.port}`, `127.0.0.1:${this.port}`];
|
||||
} else {
|
||||
this.ipResult = [`127.0.0.1:${this.port}`];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.percentage = Math.random() * 100;
|
||||
|
||||
this.defaultResolutionError = {
|
||||
code: Status.UNAVAILABLE,
|
||||
details: `Name resolution failed for target ${this.target}`,
|
||||
metadata: new Metadata(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -154,14 +203,14 @@ class DnsResolver implements Resolver {
|
|||
private startResolution() {
|
||||
if (this.ipResult !== null) {
|
||||
setImmediate(() => {
|
||||
this.listener.onSuccessfulResolution([this.ipResult!], null, null);
|
||||
this.listener.onSuccessfulResolution(this.ipResult!, null, null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.dnsHostname !== null) {
|
||||
const hostname: string = this.dnsHostname;
|
||||
const aResult = resolve4Promise(hostname);
|
||||
let aaaaResult: Promise<string[]>;
|
||||
let aaaaResult: Promise<string[] | Error>;
|
||||
if (semver.satisfies(process.version, IPV6_SUPPORT_RANGE)) {
|
||||
aaaaResult = resolve6Promise(hostname);
|
||||
} else {
|
||||
|
|
@ -183,9 +232,34 @@ class DnsResolver implements Resolver {
|
|||
this.pendingResultPromise.then(
|
||||
([aRecord, aaaaRecord, txtRecord]) => {
|
||||
this.pendingResultPromise = null;
|
||||
/* dns.resolve4 and resolve6 return an error if there are no
|
||||
* addresses for that family. If there are addresses for the other
|
||||
* family we want to use them instead of considering that an overall
|
||||
* resolution failure. The error code that indicates this situation
|
||||
* is ENODATA */
|
||||
if (aRecord instanceof Error) {
|
||||
if (aRecord.code === 'ENODATA') {
|
||||
aRecord = [];
|
||||
} else {
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (aaaaRecord instanceof Error) {
|
||||
if (aaaaRecord.code === 'ENODATA') {
|
||||
aaaaRecord = [];
|
||||
} else {
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
aRecord = aRecord.map(value => `${value}:${this.port}`);
|
||||
aaaaRecord = aaaaRecord.map(value => `[${value}]:${this.port}`);
|
||||
const allAddresses: string[] = mergeArrays(aaaaRecord, aRecord);
|
||||
if (allAddresses.length === 0) {
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
return;
|
||||
}
|
||||
let serviceConfig: ServiceConfig | null = null;
|
||||
let serviceConfigError: StatusObject | null = null;
|
||||
if (txtRecord instanceof Error) {
|
||||
|
|
@ -216,11 +290,7 @@ class DnsResolver implements Resolver {
|
|||
},
|
||||
err => {
|
||||
this.pendingResultPromise = null;
|
||||
this.listener.onError({
|
||||
code: Status.UNAVAILABLE,
|
||||
details: `Name resolution failed for target ${this.target}`,
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue