grpc-js: Fix proxy + URI parsing bugs

This commit is contained in:
Michael Lumish 2020-04-21 09:18:35 -07:00
parent 40fa1de31c
commit 726e7453f5
4 changed files with 45 additions and 42 deletions

View File

@ -33,7 +33,7 @@ import { FilterStackFactory } from './filter-stack';
import { CallCredentialsFilterFactory } from './call-credentials-filter'; import { CallCredentialsFilterFactory } from './call-credentials-filter';
import { DeadlineFilterFactory } from './deadline-filter'; import { DeadlineFilterFactory } from './deadline-filter';
import { CompressionFilterFactory } from './compression-filter'; import { CompressionFilterFactory } from './compression-filter';
import { getDefaultAuthority } from './resolver'; import { getDefaultAuthority, mapUriDefaultScheme } from './resolver';
import { ServiceConfig, validateServiceConfig } from './service-config'; import { ServiceConfig, validateServiceConfig } from './service-config';
import { trace, log } from './logging'; import { trace, log } from './logging';
import { SubchannelAddress } from './subchannel'; import { SubchannelAddress } from './subchannel';
@ -170,20 +170,18 @@ export class ChannelImplementation implements Channel {
if (originalTargetUri === null) { if (originalTargetUri === null) {
throw new Error(`Could not parse target name "${target}"`); throw new Error(`Could not parse target name "${target}"`);
} }
/* This ensures that the target has a scheme that is registered with the
* resolver */
const defaultSchemeMapResult = mapUriDefaultScheme(originalTargetUri);
if (this.options['grpc.default_authority']) { if (this.options['grpc.default_authority']) {
this.defaultAuthority = this.options['grpc.default_authority'] as string; this.defaultAuthority = this.options['grpc.default_authority'] as string;
} else { } else {
this.defaultAuthority = getDefaultAuthority(originalTargetUri); this.defaultAuthority = getDefaultAuthority(defaultSchemeMapResult);
} }
const proxyMapResult = mapProxyName(originalTargetUri, options); const proxyMapResult = mapProxyName(defaultSchemeMapResult, options);
this.target = proxyMapResult.target; this.target = proxyMapResult.target;
this.options = Object.assign({}, this.options, proxyMapResult.extraOptions); this.options = Object.assign({}, this.options, proxyMapResult.extraOptions);
const targetUri = parseUri(target);
if (targetUri === null) {
throw new Error(`Could not parse target name "${target}"`);
}
this.target = targetUri;
/* The global boolean parameter to getSubchannelPool has the inverse meaning to what /* The global boolean parameter to getSubchannelPool has the inverse meaning to what
* the grpc.use_local_subchannel_pool channel option means. */ * the grpc.use_local_subchannel_pool channel option means. */
this.subchannelPool = getSubchannelPool( this.subchannelPool = getSubchannelPool(

View File

@ -29,6 +29,7 @@ import {
} from './subchannel'; } from './subchannel';
import { ChannelOptions } from './channel-options'; import { ChannelOptions } from './channel-options';
import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser';
import { URL } from 'url';
const TRACER_NAME = 'proxy'; const TRACER_NAME = 'proxy';
@ -60,30 +61,31 @@ function getProxyInfo(): ProxyInfo {
} else { } else {
return {}; return {};
} }
const proxyUrl = parseUri(proxyEnv); let proxyUrl: URL;
if (proxyUrl === null) { try {
proxyUrl = new URL(proxyEnv);
} catch (e) {
log(LogVerbosity.ERROR, `cannot parse value of "${envVar}" env var`); log(LogVerbosity.ERROR, `cannot parse value of "${envVar}" env var`);
return {}; return {};
} }
if (proxyUrl.scheme !== 'http') { if (proxyUrl.protocol !== 'http:') {
log( log(
LogVerbosity.ERROR, LogVerbosity.ERROR,
`"${proxyUrl.scheme}" scheme not supported in proxy URI` `"${proxyUrl.protocol}" scheme not supported in proxy URI`
); );
return {}; return {};
} }
const splitPath = proxyUrl.path.split('@');
let host: string;
let userCred: string | null = null; let userCred: string | null = null;
if (splitPath.length === 2) { if (proxyUrl.username) {
log(LogVerbosity.INFO, 'userinfo found in proxy URI'); if (proxyUrl.password) {
userCred = splitPath[0]; log(LogVerbosity.INFO, 'userinfo found in proxy URI');
host = splitPath[1]; userCred = `${proxyUrl.username}:${proxyUrl.password}`;
} else { } else {
host = proxyUrl.path; userCred = proxyUrl.username;
}
} }
const result: ProxyInfo = { const result: ProxyInfo = {
address: host, address: proxyUrl.host,
}; };
if (userCred) { if (userCred) {
result.creds = userCred; result.creds = userCred;
@ -145,7 +147,10 @@ export function mapProxyName(
extraOptions['grpc.http_connect_creds'] = proxyInfo.creds; extraOptions['grpc.http_connect_creds'] = proxyInfo.creds;
} }
return { return {
target: { path: proxyInfo.address }, target: {
scheme: 'dns',
path: proxyInfo.address
},
extraOptions: extraOptions, extraOptions: extraOptions,
}; };
} }

View File

@ -18,7 +18,6 @@ import {
Resolver, Resolver,
ResolverListener, ResolverListener,
registerResolver, registerResolver,
registerDefaultResolver,
} from './resolver'; } from './resolver';
import * as dns from 'dns'; import * as dns from 'dns';
import * as util from 'util'; import * as util from 'util';
@ -281,7 +280,6 @@ class DnsResolver implements Resolver {
*/ */
export function setup(): void { export function setup(): void {
registerResolver('dns', DnsResolver); registerResolver('dns', DnsResolver);
registerDefaultResolver(DnsResolver);
} }
export interface DnsUrl { export interface DnsUrl {

View File

@ -74,7 +74,7 @@ export interface ResolverConstructor {
} }
const registeredResolvers: { [scheme: string]: ResolverConstructor } = {}; const registeredResolvers: { [scheme: string]: ResolverConstructor } = {};
let defaultResolver: ResolverConstructor | null = null; let defaultScheme: string | null = null;
/** /**
* Register a resolver class to handle target names prefixed with the `prefix` * Register a resolver class to handle target names prefixed with the `prefix`
@ -95,8 +95,8 @@ export function registerResolver(
* any registered prefix. * any registered prefix.
* @param resolverClass * @param resolverClass
*/ */
export function registerDefaultResolver(resolverClass: ResolverConstructor) { export function registerDefaultScheme(scheme: string) {
defaultResolver = resolverClass; defaultScheme = scheme;
} }
/** /**
@ -112,18 +112,10 @@ export function createResolver(
if (target.scheme !== undefined && target.scheme in registeredResolvers) { if (target.scheme !== undefined && target.scheme in registeredResolvers) {
return new registeredResolvers[target.scheme](target, listener); return new registeredResolvers[target.scheme](target, listener);
} else { } else {
if (defaultResolver !== null) { throw new Error(
/* If the scheme does not correspond to a registered scheme, we assume `No resolver could be created for target ${uriToString(target)}`
* that the whole thing is the path, and the scheme was pulled out );
* incorrectly. For example, it is valid to parse "localhost:80" as
* having a scheme of "localhost" and a path of 80, but that is not
* how the resolver should see it */
return new defaultResolver({ path: uriToString(target) }, listener);
}
} }
throw new Error(
`No resolver could be created for target ${uriToString(target)}`
);
} }
/** /**
@ -135,12 +127,22 @@ export function getDefaultAuthority(target: GrpcUri): string {
if (target.scheme !== undefined && target.scheme in registeredResolvers) { if (target.scheme !== undefined && target.scheme in registeredResolvers) {
return registeredResolvers[target.scheme].getDefaultAuthority(target); return registeredResolvers[target.scheme].getDefaultAuthority(target);
} else { } else {
if (defaultResolver !== null) { throw new Error(`Invalid target ${uriToString(target)}`);
// See comment in createResolver for why we handle the target like this }
return defaultResolver.getDefaultAuthority({ path: uriToString(target) }); }
export function mapUriDefaultScheme(target: GrpcUri): GrpcUri {
if (target.scheme === undefined || !(target.scheme in registeredResolvers)) {
if (defaultScheme !== null) {
return {
scheme: defaultScheme,
path: uriToString(target)
};
} else {
throw new Error(`Invalid target ${uriToString(target)}`);
} }
} }
throw new Error(`Invalid target ${uriToString(target)}`); return target;
} }
export function registerAll() { export function registerAll() {