{"record":{"id":"c696330ed9ad977f","repo":"fish2018/pansou","slug":"s-w-c69633","errorCode":null,"errorMessage":"[%s] 创建搜索请求失败: %w","messagePattern":"\\[(.+?)\\] 创建搜索请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/zlxapp/zlxapp.go","lineNumber":104,"sourceCode":"\treturn p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)\n}\n\nfunc (p *ZlxappPlugin) searchImpl(client *http.Client, keyword string, _ map[string]interface{}) ([]model.SearchResult, error) {\n\tkeyword = cleanText(keyword)\n\tif keyword == \"\" {\n\t\treturn []model.SearchResult{}, nil\n\t}\n\tif client == nil {\n\t\tclient = http.DefaultClient\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), requestTimeout)\n\tdefer cancel()\n\n\tsearchURL := fmt.Sprintf(\"%s/s/%s.html\", strings.TrimRight(p.baseURL, \"/\"), url.PathEscape(keyword))\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建搜索请求失败: %w\", p.Name(), err)\n\t}\n\tsetRequestHeaders(req, p.baseURL)\n\n\tresp, err := doRequestWithRetry(client, req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取搜索响应失败: %w\", p.Name(), err)\n\t}\n\tif len(body) > maxResponseSize {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索响应超过 %d 字节\", p.Name(), maxResponseSize)\n\t}\n\n\titems, err := parseListItems(body)","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/zlxapp/zlxapp.go#L86-L122","documentation":"searchImpl builds a GET request with http.NewRequestWithContext against p.baseURL + \"/s/<escaped keyword>.html\". When http.NewRequestWithContext returns a non-nil error (unparseable URL, invalid method, or a nil/already-cancelled context), the plugin wraps it with this message so the plugin name is preserved. It means the search never started — no network I/O occurred.","triggerScenarios":"Calling searchImpl (directly or via the plugin Search entry point) when the resulting searchURL cannot be parsed as a valid absolute URL — e.g. p.baseURL is empty, contains spaces/control characters, or has a malformed scheme — or when ctx is invalid.","commonSituations":"Misconfigured plugin baseURL in the app config (empty string, missing scheme like \"zlx.example.com\" instead of \"https://zlx.example.com\", trailing whitespace, or copy-pasted fullwidth characters). Also rare library/version regressions in net/http URL parsing.","solutions":["Fix the plugin's baseURL configuration so it is a valid absolute URL with a scheme, e.g. \"https://zlx.example.com\".","url.PathEscape(keyword) can still leave characters net/url rejects in some positions; test the exact keyword against url.Parse and strip/replace offending characters before calling Search.","Trim surrounding whitespace from the configured baseURL before storing it; verify the config file was loaded and the field is non-empty."],"exampleFix":"// before\nsearchURL := fmt.Sprintf(\"%s/s/%s.html\", strings.TrimRight(p.baseURL, \"/\"), url.PathEscape(keyword))\nreq, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)\n\n// after\nbase := strings.TrimSpace(p.baseURL)\nif u, perr := url.Parse(base); perr != nil || u.Scheme == \"\" || u.Host == \"\" {\n    return nil, fmt.Errorf(\"[%s] 无效的 baseURL: %q\", p.Name(), base)\n}\nsearchURL := fmt.Sprintf(\"%s/s/%s.html\", strings.TrimRight(base, \"/\"), url.PathEscape(keyword))\nreq, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)","handlingStrategy":"validation","validationCode":"u, err := url.Parse(strings.TrimSpace(p.baseURL))\nif err != nil || u.Scheme == \"\" || u.Host == \"\" {\n    return fmt.Errorf(\"invalid baseURL %q: %v\", p.baseURL, err)\n}\nif keyword == \"\" {\n    return fmt.Errorf(\"keyword must be non-empty\")\n}","typeGuard":"func validBaseURL(s string) bool {\n    u, err := url.Parse(strings.TrimSpace(s))\n    return err == nil && (u.Scheme == \"http\" || u.Scheme == \"https\") && u.Host != \"\"\n}","tryCatchPattern":"results, err := plugin.Search(ctx, keyword)\nif err != nil {\n    var opErr *url.Error\n    if errors.As(err, &opErr) && opErr.Op == \"parse\" {\n        // config problem: do not retry, surface configuration error\n        return fmt.Errorf(\"check plugin baseURL config: %w\", err)\n    }\n    return err\n}","preventionTips":["Validate baseURL at plugin init/startup, not per request","Trim whitespace and strip invisible/fullwidth characters from config values at load time","Add a startup smoke test that calls url.Parse on every configured endpoint"],"tags":["go","http","url","network","configuration"],"backgroundTag":"invalid-url","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"}