{"record":{"id":"522f1982dab881b1","repo":"fish2018/pansou","slug":"s-w-522f19","errorCode":null,"errorMessage":"[%s] 读取响应失败: %w","messagePattern":"\\[(.+?)\\] 读取响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/ash/ash.go","lineNumber":107,"sourceCode":"\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)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 提取搜索结果失败: %w\", p.Name(), err)\n\t}\n\t\n\t// 关键词过滤\n\tfiltered := plugin.FilterResultsByKeyword(results, keyword)\n\t\n\treturn filtered, nil\n}\n\n// extractResultsFromBytes 从字节数组中提取搜索结果（优化版本，避免字符串转换）\nfunc (p *AshPlugin) extractResultsFromBytes(data []byte) ([]model.SearchResult, error) {\n\t// 直接在字节数组中查找JSON数据（避免转换为字符串）\n\thtml := string(data) // 只转换一次","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/ash/ash.go#L89-L125","documentation":"Reading the ASH response body with io.ReadAll on a 2MB LimitReader failed. The plugin wraps the I/O error as '读取响应失败'. This means the connection broke mid-body (EOF, reset, unexpected EOF) rather than the content being too large — the LimitReader caps size, not correctness.","triggerScenarios":"io.ReadAll(limitReader) on resp.Body returns a non-nil error: connection reset by peer, unexpected EOF, context deadline exceeded while streaming — ash.go:107.","commonSituations":"Server closes the connection early under load or anti-bot filtering; flaky mobile/proxied network; TLS interception cutting the stream; very slow site exceeding the 15s context timeout mid-read.","solutions":["Retry the request (the existing retry loop does not cover body reading); a single re-issue usually succeeds.","Unwrap the error to distinguish context deadline exceeded (raise the 15s timeout) from connection reset (network/site issue).","Read incrementally with buffered reads so partial data and the exact failure point can be logged.","Check for proxies/middleboxes truncating responses and test from a different network."],"exampleFix":"// before\nbody, err := io.ReadAll(limitReader)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n}\n// after\nbody, err := io.ReadAll(limitReader)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        return nil, fmt.Errorf(\"[%s] 读取响应超时: %w\", p.Name(), err)\n    }\n    return nil, fmt.Errorf(\"[%s] 读取响应失败: %w\", p.Name(), err)\n}","handlingStrategy":"retry","validationCode":"// re-issue the request if body read fails once\nif err != nil && errors.Is(err, io.ErrUnexpectedEOF) { retry once }","typeGuard":null,"tryCatchPattern":"body, err := io.ReadAll(limitReader)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) { /* raise timeout, retry */ }\n    else { /* retry request; log truncated read */ }\n}","preventionTips":["Cover body reads inside the retry loop, not just the request","Use timeouts larger than worst-case streaming time","Avoid unreliable proxies for large responses","Log bytes-read count on failure for diagnosis"],"tags":["network","io","http","go"],"backgroundTag":"file-read-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"}