{"record":{"id":"ebd21013729d58fa","repo":"fish2018/pansou","slug":"s-w-ebd210","errorCode":null,"errorMessage":"[%s] 搜索请求失败: %w","messagePattern":"\\[(.+?)\\] 搜索请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/ash/ash.go","lineNumber":93,"sourceCode":"\tsearchURL := fmt.Sprintf(\"https://so.allsharehub.com/s/%s.html\", url.QueryEscape(keyword))\n\t\n\t// 创建带超时的上下文（减少超时时间，提高响应速度）\n\tctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)\n\tdefer cancel()\n\t\n\t// 创建请求\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建请求失败: %w\", p.Name(), err)\n\t}\n\t\n\t// 设置请求头\n\tp.setRequestHeaders(req)\n\t\n\t// 发送请求（优化重试）\n\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\t\n\t// 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\t\n\t// 读取响应（使用有限制的读取，避免读取过大内容）\n\t// ASH页面通常不会太大，限制在2MB以内\n\tlimitReader := io.LimitReader(resp.Body, 2*1024*1024)\n\tbody, err := io.ReadAll(limitReader)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n\t}\n\t\n\t// 从HTML中提取JSON数据（直接传递字节，避免字符串转换）\n\tresults, err := p.extractResultsFromBytes(body)","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/ash/ash.go#L75-L111","documentation":"ash's searchImpl calls doRequestWithRetry, which exhausts its retry attempts on the GET request to the ASH search endpoint. The final transport error is wrapped as '搜索请求失败'. It indicates the retries could not obtain a response at all (connection-level failure), distinct from a non-200 status (see error 33).","triggerScenarios":"p.doRequestWithRetry(req, client) returns an error after all attempts fail — connection refused/reset, DNS failure, TLS handshake error, or request context (15s timeout) expiry — ash.go:93.","commonSituations":"Upstream site is offline or geo-blocked; local network/proxy misconfigured; the 15-second context times out on a slow site; server drops keep-alive connections mid-retry.","solutions":["Unwrap the error (errors.Unwrap) to identify the root transport failure (timeout vs refused vs TLS).","Test the ASH endpoint directly with curl to check availability and latency.","Increase the 15s context timeout or the retry count/backoff if the site is merely slow.","Configure an HTTP(S) proxy if the environment requires one, and verify DNS resolution."],"exampleFix":"// before\nresp, err := p.doRequestWithRetry(req, client)\nif err != nil { return nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err) }\n// after\nresp, err := p.doRequestWithRetry(req, client)\nif err != nil {\n    log.Printf(\"[%s] search failed: %v\", p.Name(), errors.Unwrap(err))\n    return nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n}","handlingStrategy":"retry","validationCode":"// pre-check reachability\nresp, err := http.Head(baseURL); if err != nil { /* upstream down, skip search */ }","typeGuard":null,"tryCatchPattern":"err := doSearch(); if err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        // retry with larger timeout\n    } else if isConnRefused(err) {\n        // fail fast, don't retry\n    }\n}","preventionTips":["Set realistic context timeouts vs total backoff time","Probe endpoint availability before bulk runs","Configure proxies for blocked networks","Log unwrapped causes, not just the wrapped message"],"tags":["network","http","retry-exhausted","go"],"backgroundTag":"http-request-failed","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"}