{"record":{"id":"7c2cf925c17c94d7","repo":"Tencent/WeKnora","slug":"read-response-body-w-7c2cf9","errorCode":null,"errorMessage":"read response body: %w","messagePattern":"read response body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/docparser/mineru_converter.go","lineNumber":259,"sourceCode":"\n\tclient := utils.NewSSRFSafeHTTPClient(utils.SSRFSafeHTTPClientConfig{\n\t\tTimeout:      mineruTimeout,\n\t\tMaxRedirects: 5,\n\t})\n\tresp, err := client.Do(httpReq)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"HTTP request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\trespBody, _ := io.ReadAll(resp.Body)\n\t\treturn \"\", nil, fmt.Errorf(\"MinerU API status %d: %s\", resp.StatusCode, string(respBody))\n\t}\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"read response body: %w\", err)\n\t}\n\n\t// Dump raw response for debugging (truncate if too large)\n\trawStr := string(respBody)\n\tif len(rawStr) > 4000 {\n\t\tlogger.Infof(context.Background(), \"[MinerU] Raw response (truncated to 4000 chars): %s ...\", rawStr[:4000])\n\t} else {\n\t\tlogger.Infof(context.Background(), \"[MinerU] Raw response: %s\", rawStr)\n\t}\n\n\t// Also pretty-print the top-level structure (without large base64 blobs)\n\tvar rawMap map[string]interface{}\n\tif err := json.Unmarshal(respBody, &rawMap); err == nil {\n\t\tc.logMinerUResponseStructure(rawMap, \"\")\n\t}\n\n\tmdContent, imagesB64, resultKey, err := parseMinerUFileParseResponse(respBody, uploadFileName)\n\tif err != nil {","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/docparser/mineru_converter.go#L241-L277","documentation":"After MinerU returns HTTP 200, callFileParse reads the full response body with io.ReadAll. If that read fails (connection reset mid-response, context cancellation, truncated chunked transfer, or a broken pipe to the MinerU server), the converter wraps the underlying error with this message. The request succeeded at the HTTP level but the response payload could not be received completely.","triggerScenarios":"io.ReadAll(resp.Body) in callFileParse returns an error: network interruption after headers, MinerU closing the connection mid-body, context deadline exceeded while streaming a large parsed document, or proxy/gateway cutting the chunked response.","commonSituations":"Very large documents whose parsed JSON/zip takes long to stream and hits an idle-timeout on a proxy; unstable network to a remote MinerU instance; context canceled because the user request was aborted upstream.","solutions":["Retry the parse request — transient resets usually succeed on a second attempt","Increase the client timeout and any proxy idle/read timeouts between the app and MinerU","Check that no reverse proxy (nginx/ingress) is buffering or truncating large responses","Ensure the caller's context is not cancelled prematurely (increase upstream deadline)","Verify network stability/DNS to the MinerU host"],"exampleFix":"// before\nclient := &http.Client{Timeout: 10 * time.Second}\n// after\nclient := &http.Client{Timeout: 300 * time.Second} // allow large MinerU responses to stream","handlingStrategy":"retry","validationCode":"client := &http.Client{Timeout: 300 * time.Second}\nreq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, body)\nif _, err := client.Do(req); err != nil {\n    if errors.Is(err, context.DeadlineExceeded) { /* increase deadline */ }\n}","typeGuard":"func isBodyReadError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"read response body\")\n}","tryCatchPattern":"result, err := callFileParse(ctx, file)\nif isBodyReadError(err) {\n    result, err = retryWithBackoff(3, callFileParse, ctx, file)\n}\nif err != nil { return err }","preventionTips":["Set generous client timeouts for large parsed documents","Align proxy idle timeouts with expected response duration","Check context cancellation propagation before long parses","Prefer streaming reads over long-lived idle connections"],"tags":["network","http","io","mineru","docparser"],"backgroundTag":"response-body-read-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}