{"record":{"id":"466e7a52135ff995","repo":"fish2018/pansou","slug":"s-w-466e7a","errorCode":null,"errorMessage":"[%s] 读取响应失败: %w","messagePattern":"\\[(.+?)\\] 读取响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/clmao/clmao.go","lineNumber":200,"sourceCode":"\t// 设置请求头\n\tp.setRequestHeaders(req)\n\n\t// 发送HTTP请求\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\n\t// 检查状态码\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"[%s] 请求返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\t// 读取响应体内容\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n\t}\n\n\tdecodedHTML := decodeModernPayload(string(body))\n\tif decodedHTML != string(body) {\n\t\tif modernResults := p.parseModernSearchResults(client, decodedHTML); len(modernResults) > 0 {\n\t\t\treturn modernResults, nil\n\t\t}\n\t}\n\n\t// 兼容旧模板\n\tdoc, err := goquery.NewDocumentFromReader(strings.NewReader(decodedHTML))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] HTML解析失败: %w\", p.Name(), err)\n\t}\n\n\t// 提取搜索结果\n\treturn p.extractSearchResults(doc), nil\n}","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/clmao/clmao.go#L182-L218","documentation":"ClmaoPlugin.searchPage fails while reading the HTTP response body with io.ReadAll. Since the status already passed the 200 check, this is almost always a mid-stream network failure (connection reset, unexpected EOF) or the request context expiring while the body is still streaming.","triggerScenarios":"After a 200 response, body, err := io.ReadAll(resp.Body) errors — the connection dropped mid-transfer, the server truncated the response, or ctx (TimeoutSeconds) expired during body read — producing \"[clmao] 读取响应失败: %w\".","commonSituations":"Slow/large responses exceeding the TimeoutSeconds budget (deadline hits while reading); flaky networks or proxies closing connections; anti-bot middleboxes resetting the connection after headers were sent.","solutions":["Check the wrapped error: context.DeadlineExceeded means the timeout hit during body read — increase TimeoutSeconds.","Retry the request; mid-stream resets are often transient and re-entering searchPage reuses doRequestWithRetry.","Use io.LimitReader to bound body size and avoid pathological huge reads.","Ensure resp.Body is fully drained/closed promptly so keep-alive connections aren't broken."],"exampleFix":"// before\nbody, err := io.ReadAll(resp.Body)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n}\n// after\nbody, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        return nil, fmt.Errorf(\"[%s] 读取响应超时，尝试增大 TimeoutSeconds: %w\", p.Name(), err)\n    }\n    return nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"body, err := io.ReadAll(resp.Body)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) {\n        return retryOnce(req) // transient mid-stream failure\n    }\n    return nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n}","preventionTips":["Set TimeoutSeconds high enough to cover full body download, not just headers.","Bound response size with io.LimitReader to avoid pathological reads.","Treat mid-stream resets as transient and retry.","Drain and close response bodies promptly to keep connections reusable."],"tags":["network","http","io","timeout"],"backgroundTag":"network-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"}