diff --git a/clientconn.go b/clientconn.go index 096d9434..64a7982f 100644 --- a/clientconn.go +++ b/clientconn.go @@ -487,7 +487,25 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * if cc.dopts.bs == nil { cc.dopts.bs = DefaultBackoffConfig } - cc.parsedTarget = parseTarget(cc.target) + if cc.dopts.resolverBuilder == nil { + // Only try to parse target when resolver builder is not already set. + cc.parsedTarget = parseTarget(cc.target) + grpclog.Infof("parsed scheme: %q", cc.parsedTarget.Scheme) + cc.dopts.resolverBuilder = resolver.Get(cc.parsedTarget.Scheme) + if cc.dopts.resolverBuilder == nil { + // If resolver builder is still nil, the parse target's scheme is + // not registered. Fallback to default resolver and set Endpoint to + // the original unparsed target. + grpclog.Infof("scheme %q not registered, fallback to default scheme", cc.parsedTarget.Scheme) + cc.parsedTarget = resolver.Target{ + Scheme: resolver.GetDefaultScheme(), + Endpoint: target, + } + cc.dopts.resolverBuilder = resolver.Get(cc.parsedTarget.Scheme) + } + } else { + cc.parsedTarget = resolver.Target{Endpoint: target} + } creds := cc.dopts.copts.TransportCredentials if creds != nil && creds.Info().ServerName != "" { cc.authority = creds.Info().ServerName diff --git a/resolver/resolver.go b/resolver/resolver.go index 9efcffb3..775ee4d0 100644 --- a/resolver/resolver.go +++ b/resolver/resolver.go @@ -36,21 +36,12 @@ func Register(b Builder) { } // Get returns the resolver builder registered with the given scheme. -// If no builder is register with the scheme, the default scheme will -// be used. -// If the default scheme is not modified, "passthrough" will be the default -// scheme, and the preinstalled dns resolver will be used. -// If the default scheme is modified, and a resolver is registered with -// the scheme, that resolver will be returned. -// If the default scheme is modified, and no resolver is registered with -// the scheme, nil will be returned. +// +// If no builder is register with the scheme, nil will be returned. func Get(scheme string) Builder { if b, ok := m[scheme]; ok { return b } - if b, ok := m[defaultScheme]; ok { - return b - } return nil } @@ -60,6 +51,11 @@ func SetDefaultScheme(scheme string) { defaultScheme = scheme } +// GetDefaultScheme gets the default scheme that will be used. +func GetDefaultScheme() string { + return defaultScheme +} + // AddressType indicates the address type returned by name resolution. type AddressType uint8 diff --git a/resolver_conn_wrapper.go b/resolver_conn_wrapper.go index 3e9c7594..75b8ce1e 100644 --- a/resolver_conn_wrapper.go +++ b/resolver_conn_wrapper.go @@ -48,6 +48,9 @@ func split2(s, sep string) (string, string, bool) { // parseTarget splits target into a struct containing scheme, authority and // endpoint. +// +// If target is not a valid scheme://authority/endpoint, it returns {Endpoint: +// target}. func parseTarget(target string) (ret resolver.Target) { var ok bool ret.Scheme, ret.Endpoint, ok = split2(target, "://") @@ -68,14 +71,9 @@ func parseTarget(target string) (ret resolver.Target) { // If withResolverBuilder dial option is set, the specified resolver will be // used instead. func newCCResolverWrapper(cc *ClientConn) (*ccResolverWrapper, error) { - grpclog.Infof("dialing to target with scheme: %q", cc.parsedTarget.Scheme) - rb := cc.dopts.resolverBuilder if rb == nil { - rb = resolver.Get(cc.parsedTarget.Scheme) - if rb == nil { - return nil, fmt.Errorf("could not get resolver for scheme: %q", cc.parsedTarget.Scheme) - } + return nil, fmt.Errorf("could not get resolver for scheme: %q", cc.parsedTarget.Scheme) } ccr := &ccResolverWrapper{ diff --git a/resolver_conn_wrapper_test.go b/resolver_conn_wrapper_test.go index 3752c420..1109804b 100644 --- a/resolver_conn_wrapper_test.go +++ b/resolver_conn_wrapper_test.go @@ -19,25 +19,20 @@ package grpc import ( + "fmt" + "net" "testing" + "time" "google.golang.org/grpc/resolver" ) func TestParseTarget(t *testing.T) { for _, test := range []resolver.Target{ - {"", "", ""}, - {"a", "", ""}, - {"", "a", ""}, - {"", "", "a"}, - {"a", "b", ""}, - {"a", "", "b"}, - {"", "a", "b"}, - {"a", "b", "c"}, {"dns", "", "google.com"}, {"dns", "a.server.com", "google.com"}, {"dns", "a.server.com", "google.com/?a=b"}, - {"", "", "/unix/socket/address"}, + {"passthrough", "", "/unix/socket/address"}, } { str := test.Scheme + "://" + test.Authority + "/" + test.Endpoint got := parseTarget(str) @@ -85,3 +80,34 @@ func TestParseTargetString(t *testing.T) { } } } + +// The target string with unknown scheme should be kept unchanged and passed to +// the dialer. +func TestDialParseTargetUnknownScheme(t *testing.T) { + for _, test := range []struct { + targetStr string + want string + }{ + {"/unix/socket/address", "/unix/socket/address"}, + + // Special test for "unix:///". + {"unix:///unix/socket/address", "unix:///unix/socket/address"}, + + // For known scheme. + {"passthrough://a.server.com/google.com", "google.com"}, + } { + dialStrCh := make(chan string, 1) + cc, err := Dial(test.targetStr, WithInsecure(), WithDialer(func(t string, _ time.Duration) (net.Conn, error) { + dialStrCh <- t + return nil, fmt.Errorf("test dialer, always error") + })) + if err != nil { + t.Fatalf("Failed to create ClientConn: %v", err) + } + got := <-dialStrCh + cc.Close() + if got != test.want { + t.Errorf("Dial(%q), dialer got %q, want %q", test.targetStr, got, test.want) + } + } +}