grpc/grpc-go · error

rls: invalid target URI in lookup_service

Error message

rls: invalid target URI in lookup_service %s

What it means

Emitted from parseRLSProto (config.go:203) when the lookup_service string cannot be parsed as a URI even after the balancer retries by prepending the default resolver scheme ('<default>:///' + lookupService). This is the fallback path; it fires when url.Parse rejects the string in both forms, i.e. the string contains control characters or other bytes that make it an invalid URI.

Solutions

  1. Strip whitespace/newlines from the lookup_service value and re-test with url.Parse locally.
  2. Provide a fully-qualified URI with an explicit scheme and authority, e.g. 'dns:///rls.example.com:443'.
  3. Percent-encode any characters that are legal in a hostname but not in a URI path/query if they appear.

Example fix

// before
"lookupService": "rls.example.com:443 "

// after
"lookupService": "dns:///rls.example.com:443"
Defensive patterns

Strategy: validation

Validate before calling

func parseLookupServiceURI(s string) (*url.URL, error) {
    u, err := url.Parse(s)
    if err != nil {
        u, err = url.Parse(resolver.GetDefaultScheme() + "/" + s)
        if err != nil {
            return nil, fmt.Errorf("lookup_service %q is not a valid URI: %w", s, err)
        }
    }
    return u, nil
}

Type guard

func isParsableLookupURI(s string) bool {
    _, err := parseLookupServiceURI(s)
    return err == nil
}

Prevention

When it happens

Trigger: lookupService contains characters that url.Parse refuses (e.g. raw spaces, control bytes, or malformed percent-encoding) in both the bare form and the scheme-prepended form.

Common situations: A copy-paste of a hostname with a stray space or newline; percent-encoding errors; a value that is actually a full connection string with extra metadata rather than a host:port / URI.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/678ff82cd0fc3db1. Report an issue: GitHub.

Appendix: source

Thrown at balancer/rls/config.go:203

func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {
	// Validations specified on the `grpc_keybuilders` field are performed here.
	kbMap, err := keys.MakeBuilderMap(rlsProto)
	if err != nil {
		return nil, err
	}

	// `lookup_service` field must be set and must parse as a target URI.
	lookupService := rlsProto.GetLookupService()
	if lookupService == "" {
		return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto)
	}
	parsedTarget, err := url.Parse(lookupService)
	if err != nil {
		// url.Parse() fails if scheme is missing. Retry with default scheme.
		parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
		if err != nil {
			return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
		}
	}
	if parsedTarget.Scheme == "" {
		parsedTarget.Scheme = resolver.GetDefaultScheme()
	}
	if resolver.Get(parsedTarget.Scheme) == nil {
		return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
	}

	lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
	if err != nil {
		return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err)
	}
	if lookupServiceTimeout == 0 {
		lookupServiceTimeout = defaultLookupServiceTimeout
	}

	// Validations performed here:

View on GitHub (pinned to 0c51461d27)