{"record":{"id":"b5d136e7074f2efe","repo":"fish2018/pansou","slug":"s-w-b5d136","errorCode":null,"errorMessage":"[%s] 读取响应失败: %w","messagePattern":"\\[(.+?)\\] 读取响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/ouge/ouge.go","lineNumber":149,"sourceCode":"\t// 设置请求头\n\treq.Header.Set(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\")\n\treq.Header.Set(\"Accept\", \"application/json, text/plain, */*\")\n\treq.Header.Set(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\")\n\treq.Header.Set(\"Connection\", \"keep-alive\")\n\treq.Header.Set(\"Referer\", \"https://woog.nxog.eu.org/\")\n\treq.Header.Set(\"Cache-Control\", \"no-cache\")\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// 解析JSON响应\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\t\n\tvar apiResponse OugeAPIResponse\n\tif err := json.Unmarshal(body, &apiResponse); err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析JSON响应失败: %w\", p.Name(), err)\n\t}\n\t\n\t// 检查API响应状态\n\tif apiResponse.Code != 1 {\n\t\treturn nil, fmt.Errorf(\"[%s] API返回错误: %s\", p.Name(), apiResponse.Msg)\n\t}\n\t\n\t// 解析搜索结果\n\tvar results []model.SearchResult\n\tfor _, item := range apiResponse.List {\n\t\tif result := p.parseAPIItem(item); result.Title != \"\" {\n\t\t\tresults = append(results, result)\n\t\t}","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/ouge/ouge.go#L131-L167","documentation":"Thrown when io.ReadAll(resp.Body) fails after a successful HTTP response from the ouge API. The HTTP layer worked but the response body stream could not be fully read, typically because the connection was reset or closed mid-transfer. The plugin wraps the io error with its plugin name for identification.","triggerScenarios":"searchImpl reads resp.Body after doRequestWithRetry returned 200; the server closes the connection prematurely, a proxy truncates the stream, or the read times out mid-body (unexpected EOF / connection reset by peer).","commonSituations":"Flaky mobile/VPN connections; large result sets over an unstable link; server-side load balancer idle-timeout killing the connection; gzip/transfer encoding interrupted by a middlebox.","solutions":["Retry the search — the error is usually transient connection interruption","Check network stability (VPN/proxy) between the client and woog.nxog.eu.org","Verify the server is not closing connections early (curl -v the same endpoint)","Add a bounded retry with backoff around searchImpl at the caller level"],"exampleFix":"// before\nbody, err := io.ReadAll(resp.Body)\nif err != nil { return err }\n// after\nbody, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize))\nif err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) { return retrySearch() }\n    return err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET) {\n        return retryWithBackoff(search, 3, 500*time.Millisecond)\n    }\n    return err\n}","preventionTips":["Use a stable wired connection or reliable proxy when scraping","Bound body reads with io.LimitReader to avoid huge truncated transfers","Retry transient io errors automatically","Watch for repeated truncation signaling a middlebox problem"],"tags":["network","io","http-response","plugin"],"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"}