{"record":{"id":"890cc69ad862e22e","repo":"jaegertracing/jaeger","slug":"server-url-q-must-include-a-scheme-and-host-e-g","errorCode":null,"errorMessage":"server URL %q must include a scheme and host, e.g. http://host:9200","messagePattern":"server URL %q must include a scheme and host, e\\.g\\. http://host:9200","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/elasticsearch/esclient/transport.go","lineNumber":70,"sourceCode":"\tlogger   *zap.Logger\n}\n\n// newRawClient builds a rawClient that round-robins requests across servers,\n// sending each through base.\nfunc newRawClient(ctx context.Context, base http.RoundTripper, opts rawClientOptions) (*rawClient, error) {\n\tif len(opts.servers) == 0 {\n\t\treturn nil, errors.New(\"no servers specified\")\n\t}\n\turls := make([]*url.URL, 0, len(opts.servers))\n\tfor _, server := range opts.servers {\n\t\tu, err := url.Parse(server)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid server URL %q: %w\", server, err)\n\t\t}\n\t\t// url.Parse accepts host:port or bare hosts as scheme/path-only URLs; the\n\t\t// pool needs a scheme and host, so reject those up front with a clear error.\n\t\tif u.Scheme == \"\" || u.Host == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"server URL %q must include a scheme and host, e.g. http://host:9200\", server)\n\t\t}\n\t\turls = append(urls, u)\n\t}\n\t// Retry is disabled to preserve the current admin-client behavior; the data\n\t// plane can opt into the pool's read retry when it adopts rawClient in Stage B.\n\ttransportOpts := []elastictransport.Option{\n\t\telastictransport.WithURLs(urls...),\n\t\telastictransport.WithTransport(base),\n\t\telastictransport.WithDisableRetry(),\n\t}\n\tif opts.compressRequestBody {\n\t\t// Defaults to gzip.DefaultCompression, matching the level the olivere\n\t\t// client used before it was retired. The pool gzips the body and sets\n\t\t// Content-Encoding before handing the request to base, so the auth stack\n\t\t// below (notably the SigV4 signer) signs the compressed payload that\n\t\t// actually goes on the wire.\n\t\ttransportOpts = append(transportOpts, elastictransport.WithCompression())\n\t}","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/jaegertracing/jaeger/blob/806f4447841ecdb60519f408b004a599d515f437/internal/storage/elasticsearch/esclient/transport.go#L52-L88","documentation":"newRawClient validates each configured Elasticsearch server URL after url.Parse. Because url.Parse accepts bare hosts and host:port strings as scheme-less/path-only URLs, the client explicitly rejects any URL missing a scheme or host so the connection pool always has well-formed node endpoints. The error names the offending server string and shows the expected shape (e.g. http://host:9200).","triggerScenarios":"Calling NewClient (via newRawClient) with a server entry like \"localhost:9200\", \"es-cluster\", \":9200\", or an empty string — any URL that parses but lacks u.Scheme or u.Host.","commonSituations":"Config files where SPAN_STORAGE_TYPE=elasticsearch servers are set without the http:// or https:// prefix; YAML/env config where the scheme was dropped during migration from an older client that tolerated bare hosts; template variables that rendered to an empty or partial host.","solutions":["Add the scheme to every server URL, e.g. change \"localhost:9200\" to \"http://localhost:9200\" (or \"https://...\" for TLS).","Check the config source (CLI flag --es.servers, env var, YAML) for values missing the scheme or with empty entries.","Ensure no template/env expansion produced an empty or truncated host before the scheme, e.g. \"http://\" alone is also rejected because Host is empty."],"exampleFix":"// before\nservers: [\"es-cluster:9200\"]\n// after\nservers: [\"http://es-cluster:9200\"]","handlingStrategy":"validation","validationCode":"for _, s := range cfg.Servers {\n\tu, err := url.Parse(s)\n\tif err != nil || u.Scheme == \"\" || u.Host == \"\" {\n\t\treturn fmt.Errorf(\"server %q must be an absolute URL, e.g. http://host:9200\", s)\n\t}\n}","typeGuard":"func isValidServerURL(s string) bool {\n\tu, err := url.Parse(s)\n\treturn err == nil && u.Scheme != \"\" && u.Host != \"\"\n}","tryCatchPattern":null,"preventionTips":["Always write server URLs with an explicit http:// or https:// scheme in config files and env vars.","Add a startup config validation step that rejects scheme-less hosts before constructing the storage client.","Lint CI configs for elasticsearch server entries missing a scheme.","Never build server URLs by string concatenation that can drop the prefix (e.g. fmt.Sprintf(\"%s:9200\", host))."],"tags":["config","url-validation","elasticsearch"],"backgroundTag":"invalid-server-url","analyzedSha":"806f4447841ecdb60519f408b004a599d515f437","analyzedAt":"2026-09-01T02:39:22.140Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}