{"record":{"id":"5a18a89393017bcd","repo":"fish2018/pansou","slug":"error-5a18a8","errorCode":null,"errorMessage":"代理地址必须包含协议和主机","messagePattern":"代理地址必须包含协议和主机","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/http_util.go","lineNumber":87,"sourceCode":"\t\tTransport: transport,\n\t\tTimeout:   time.Duration(60) * time.Second,\n\t}\n\n\treturn client, nil\n}\n\nfunc applyProxy(transport *http.Transport, rawProxyURL string) error {\n\trawProxyURL = strings.TrimSpace(rawProxyURL)\n\tif rawProxyURL == \"\" {\n\t\treturn nil\n\t}\n\n\tproxyURL, err := url.Parse(rawProxyURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"代理地址解析失败: %w\", err)\n\t}\n\tif proxyURL.Scheme == \"\" || proxyURL.Host == \"\" {\n\t\treturn fmt.Errorf(\"代理地址必须包含协议和主机\")\n\t}\n\n\tswitch strings.ToLower(proxyURL.Scheme) {\n\tcase \"socks5\", \"socks5h\":\n\t\tif proxyURL.Scheme == \"socks5h\" {\n\t\t\tclone := *proxyURL\n\t\t\tclone.Scheme = \"socks5\"\n\t\t\tproxyURL = &clone\n\t\t}\n\n\t\t// 创建SOCKS5代理拨号器\n\t\tdialer, err := proxy.FromURL(proxyURL, proxy.Direct)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"SOCKS5代理初始化失败: %w\", err)\n\t\t}\n\n\t\ttransport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\treturn dialer.Dial(network, addr)","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/util/http_util.go#L69-L105","documentation":"After url.Parse succeeds, applyProxy requires the parsed proxy URL to have both a scheme and a host. This error is returned when the string parses but is missing one of them, e.g. '127.0.0.1:8080' (no scheme) or 'socks5://' (no host). Go's url.Parse is lenient, so this explicit check catches proxy strings that are not usable as proxy endpoints.","triggerScenarios":"NewHTTPClient called with a proxy like '127.0.0.1:8080' (missing scheme), 'http://' (missing host), an empty-ish string with only a fragment, or 'localhost:8080' where 'localhost' is parsed as the scheme rather than the host.","commonSituations":"Users copying a browser PAC-style 'host:port' proxy setting into config; omitting 'http://' when configuring; env vars like HTTPS_PROXY set to a bare 'host:port' value; documentation examples that show proxies without a scheme.","solutions":["Add the scheme prefix to the proxy string, e.g. '127.0.0.1:8080' -> 'http://127.0.0.1:8080'.","Ensure the host (and port) are present after the scheme, e.g. 'socks5://127.0.0.1:1080'.","Pre-validate in your own code: parse the URL and check u.Scheme != \"\" && u.Host != \"\" before calling NewHTTPClient.","If the value comes from config, document/enforce the full 'scheme://host:port' form."],"exampleFix":"// before\nclient, err := NewHTTPClient(WithProxy(\"127.0.0.1:8080\"))\n\n// after\nclient, err := NewHTTPClient(WithProxy(\"http://127.0.0.1:8080\"))","handlingStrategy":"validation","validationCode":"if u, err := url.Parse(cfg.Proxy); err != nil || u.Scheme == \"\" || u.Host == \"\" {\n    return fmt.Errorf(\"proxy must be scheme://host:port, got %q\", cfg.Proxy)\n}","typeGuard":null,"tryCatchPattern":"if err := NewHTTPClient(WithProxy(cfg.Proxy)); err != nil {\n    if strings.Contains(err.Error(), \"协议和主机\") {\n        // fall back to direct connection or fix config\n    }\n    return err\n}","preventionTips":["Never configure a proxy without an explicit scheme prefix.","Remember url.Parse treats bare 'host:port' as scheme=host — always add 'http://'.","Document the expected 'scheme://host:port' format wherever proxies are configured."],"tags":["network","proxy","configuration","url-validation"],"backgroundTag":"invalid-url-format","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}